From ab45082a3f9f3bcb2f403a75c721ed409862ce65 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Fri, 19 Sep 2025 16:20:04 +0200 Subject: [PATCH 01/40] move GeomechnicsCalculator to geos-mesh --- .../src/geos/mesh/utils}/GeomechanicsCalculator.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {geos-posp/src/geos_posp/filters => geos-mesh/src/geos/mesh/utils}/GeomechanicsCalculator.py (100%) diff --git a/geos-posp/src/geos_posp/filters/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/utils/GeomechanicsCalculator.py similarity index 100% rename from geos-posp/src/geos_posp/filters/GeomechanicsCalculator.py rename to geos-mesh/src/geos/mesh/utils/GeomechanicsCalculator.py From bfd0196242ea4b6d34431e360f88f8868855da06 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Fri, 19 Sep 2025 16:21:53 +0200 Subject: [PATCH 02/40] move PVGeomechanicsAnalysis in geos-pv --- .../src/geos/pv/plugins}/PVGeomechanicsAnalysis.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {geos-posp/src/PVplugins => geos-pv/src/geos/pv/plugins}/PVGeomechanicsAnalysis.py (100%) diff --git a/geos-posp/src/PVplugins/PVGeomechanicsAnalysis.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsAnalysis.py similarity index 100% rename from geos-posp/src/PVplugins/PVGeomechanicsAnalysis.py rename to geos-pv/src/geos/pv/plugins/PVGeomechanicsAnalysis.py From 2836b320171faee9e79364d1a11053a4ad100572 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Mon, 22 Sep 2025 09:24:28 +0200 Subject: [PATCH 03/40] move GeomechanicsCalculator in the correct folder --- .../src/geos/mesh/{utils => processing}/GeomechanicsCalculator.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename geos-mesh/src/geos/mesh/{utils => processing}/GeomechanicsCalculator.py (100%) diff --git a/geos-mesh/src/geos/mesh/utils/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py similarity index 100% rename from geos-mesh/src/geos/mesh/utils/GeomechanicsCalculator.py rename to geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py From 7cc21f7db7d9be661dd06eb672cb2c9b28297926 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Mon, 22 Sep 2025 13:16:16 +0200 Subject: [PATCH 04/40] Remove PVPythonAlgorithmBase --- .../mesh/processing/GeomechanicsCalculator.py | 634 +++++++----------- 1 file changed, 249 insertions(+), 385 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index fb80122ad..fbe03afe5 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -5,8 +5,11 @@ from typing import Union import geos.geomechanics.processing.geomechanicsCalculatorFunctions as fcts + import numpy as np import numpy.typing as npt +import logging + from geos.utils.GeosOutputsConstants import ( AttributeEnum, ComponentNameEnum, @@ -49,7 +52,7 @@ .. code-block:: python import numpy as np - from geos_posp.filters.GeomechanicsCalculator import GeomechanicsCalculator + from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator # filter inputs logger :Logger @@ -64,7 +67,7 @@ # friction angle in ° frictionAngle :float = 10 * np.pi / 180. - # instanciate the filter + # instantiate the filter geomechanicsCalculatorFilter :GeomechanicsCalculator = GeomechanicsCalculator() # set filter attributes @@ -75,7 +78,7 @@ # set computeAdvancedOutputsOn or computeAdvancedOutputsOff to compute or # not advanced outputs... geomechanicsCalculatorFilter.computeAdvancedOutputsOn() - # set oter parameters + # set other parameters geomechanicsCalculatorFilter.SetGrainBulkModulus(grainBulkModulus) geomechanicsCalculatorFilter.SetSpecificDensity(specificDensity) # rock cohesion and friction angle are used for advanced outputs only @@ -87,23 +90,36 @@ output :Union[vtkPointSet, vtkUnstructuredGrid] = geomechanicsCalculatorFilter.GetOutputDataObject(0) """ -TYPE_ERROR_MESSAGE = ( "Input object must by either a vtkPointSet or a vtkUntructuredGrid." ) +TYPE_ERROR_MESSAGE = ( "Input object must by either a vtkPointSet or a vtkUnstructuredGrid." ) UNDEFINED_ATTRIBUTE_MESSAGE = " attribute is undefined." +loggerTitle: str = "Geomechanical Calculator Filter" + -class GeomechanicsCalculator( VTKPythonAlgorithmBase ): +class GeomechanicsCalculator(): - def __init__( self: Self ) -> None: + def __init__( + self: Self, + mesh: Union[ vtkPointSet, vtkUnstructuredGrid ], + computeAdvancedOutputs: bool = False, + speHandler: bool = False, + ) -> None: """VTK Filter to perform Geomechanical output computation. - Input object is either a vtkPointSet or a vtkUntructuredGrid. + Args: + mesh (Union[vtkPointsSet, vtkUnstructuredGrid]): Input mesh. + computeAdvancedOutputs (bool, optional): True to compute advanced geomechanical parameters, False otherwise. + Defaults to False. + speHandler (bool, optional): True to use a specific handler, False to use the internal handler. + Defaults to False. """ - super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkDataSet" ) # type: ignore[no-untyped-call] - self.m_output: Union[ vtkPointSet, vtkUnstructuredGrid ] + self.mesh: Union[ vtkPointSet, vtkUnstructuredGrid ] = mesh + self.output: Union[ vtkPointSet, vtkUnstructuredGrid ] = vtkDataSet() + self.output.DeepCopy( self.mesh ) # additional parameters - self.m_computeAdvancedOutputs: bool = False + self.m_computeAdvancedOutputs: bool = computeAdvancedOutputs self.m_grainBulkModulus: float = DEFAULT_GRAIN_BULK_MODULUS self.m_specificDensity: float = WATER_DENSITY self.m_rockCohesion: float = DEFAULT_ROCK_COHESION @@ -117,7 +133,7 @@ def __init__( self: Self ) -> None: self.m_totalStressComputed: bool = False self.m_effectiveStressRatioOedComputed: bool = False - # will compute resuls if m_ready is True (updated by initFilter method) + # will compute results if m_ready is True (updated by initFilter method) self.m_ready: bool = False # attributes are either on points or on cells self.m_attributeOnPoints: bool = False @@ -125,250 +141,131 @@ def __init__( self: Self ) -> None: # or young Modulus and poisson's ratio (m_computeYoungPoisson=False) self.m_computeYoungPoisson: bool = True - # set m_logger - self.m_logger: Logger = getLogger( "Geomechanics calculator" ) - - def FillInputPortInformation( self: Self, port: int, info: vtkInformation ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestInformation. - - Args: - port (int): input port - info (vtkInformationVector): info - - Returns: - int: 1 if calculation successfully ended, 0 otherwise. - """ - if port == 0: - info.Set( self.INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet" ) - return 1 + # Logger. + self.m_logger: Logger + if not speHandler: + self.m_logger = getLogger( loggerTitle, True ) + else: + self.m_logger = logging.getLogger( loggerTitle ) + self.m_logger.setLevel( logging.INFO ) - def RequestInformation( - self: Self, - request: vtkInformation, # noqa: F841 - inInfoVec: list[ vtkInformationVector ], # noqa: F841 - outInfoVec: vtkInformationVector, - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestInformation. - Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects + def applyFilter( self: Self ) -> bool: + """Compute the geomechanical coefficient of the mesh. Returns: - int: 1 if calculation successfully ended, 0 otherwise. + Boolean (bool): True if calculation successfully ended, False otherwise. """ - executive = self.GetExecutive() # noqa: F841 - outInfo = outInfoVec.GetInformationObject( 0 ) # noqa: F841 - return 1 - - def RequestDataObject( - self: Self, - request: vtkInformation, - inInfoVec: list[ vtkInformationVector ], - outInfoVec: vtkInformationVector, - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestDataObject. - - Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects + if not self.checkMandatoryAttributes(): + mess: str = ( "Mandatory properties are missing to compute geomechanical outputs:" ) + mess += ( f"Either {PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName} " + f"and {PostProcessingOutputsEnum.POISSON_RATIO.attributeName} or " + f"{GeosMeshOutputsEnum.BULK_MODULUS.attributeName} and " + f"{GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName} must be " + f"present in the data as well as the " + f"{GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName} attribute." ) + self.m_logger.error( mess ) + return False - Returns: - int: 1 if calculation successfully ended, 0 otherwise. - """ - inData = self.GetInputData( inInfoVec, 0, 0 ) # type: ignore[no-untyped-call] - outData = self.GetOutputData( outInfoVec, 0 ) # type: ignore[no-untyped-call] - assert inData is not None - if outData is None or ( not outData.IsA( inData.GetClassName() ) ): - outData = inData.NewInstance() - outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) - return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore - - def RequestData( - self: Self, - request: vtkInformation, - inInfoVec: list[ vtkInformationVector ], - outInfoVec: vtkInformationVector, - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestData. + if not self.computeBasicOutputs(): + return False - Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects + if self.m_computeAdvancedOutputs: + if not self.computeAdvancedOutputs(): + return False - Returns: - int: 1 if calculation successfully ended, 0 otherwise. - """ - try: - input: vtkDataSet = vtkDataSet.GetData( inInfoVec[ 0 ] ) - assert input is not None, "Input object is null." - - # initialize output objects - self.m_output = self.GetOutputData( outInfoVec, 0 ) # type: ignore[no-untyped-call] - assert self.m_output is not None, "Output object is null." - self.m_output.ShallowCopy( input ) - - # check the input and update self.m_ready, m_attributeOnPoints and m_computeBulkAndShear - self.initFilter() - if not self.m_ready: - mess: str = ( "Mandatory properties are missing to compute geomechanical outputs:" ) - mess += ( f"Either {PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName} " - f"and {PostProcessingOutputsEnum.POISSON_RATIO.attributeName} or " - f"{GeosMeshOutputsEnum.BULK_MODULUS.attributeName} and " - f"{GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName} must be " - f"present in the data as well as the " - f"{GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName} attribute." ) - self.m_logger.error( mess ) - return 0 - - self.computeBasicOutputs() - if self.m_computeAdvancedOutputs: - self.computeAdvancedOutputs() + return True - except AssertionError as e: - mess1: str = "Geomechanical attribute calculation failed due to:" - self.m_logger.error( mess1 ) - self.m_logger.error( str( e ) ) - return 0 - except Exception as e: - mess2: str = "Geomechanical attribut calculation failed due to:" - self.m_logger.critical( mess2 ) - self.m_logger.critical( e, exc_info=True ) - return 0 - return 1 + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: + """Set a specific handler for the filter logger. - def SetLogger( self: Self, logger: Logger ) -> None: - """Set the logger. + In this filter 4 log levels are use, .info, .error, .warning and .critical, be sure to have at least the same 4 levels. Args: - logger (Logger): logger + handler (logging.Handler): The handler to add. """ - self.m_logger = logger - self.Modified() - - def computeAdvancedOutputsOn( self: Self ) -> None: - """Activate advanced outputs calculation.""" - self.m_computeAdvancedOutputs = True - self.Modified() - - def computeAdvancedOutputsOff( self: Self ) -> None: - """Deactivate advanced outputs calculation.""" - self.m_computeAdvancedOutputs = False - self.Modified() + if not self.m_logger.hasHandlers(): + self.m_logger.addHandler( handler ) + else: + self.m_logger.warning( + "The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization." + ) - def SetGrainBulkModulus( self: Self, grainBulkModulus: float ) -> None: + def setGrainBulkModulus( self: Self, grainBulkModulus: float ) -> None: """Set the grain bulk modulus. Args: - grainBulkModulus (float): grain bulk modulus + grainBulkModulus (float): Grain bulk modulus. """ self.m_grainBulkModulus = grainBulkModulus - self.Modified() - def SetSpecificDensity( self: Self, specificDensity: float ) -> None: + def setSpecificDensity( self: Self, specificDensity: float ) -> None: """Set the specific density. Args: - specificDensity (float): pecific density + specificDensity (float): Specific density. """ self.m_specificDensity = specificDensity - self.Modified() - def SetRockCohesion( self: Self, rockCohesion: float ) -> None: + def setRockCohesion( self: Self, rockCohesion: float ) -> None: """Set the rock cohesion. Args: - rockCohesion (float): rock cohesion + rockCohesion (float): Rock cohesion. """ self.m_rockCohesion = rockCohesion - self.Modified() - def SetFrictionAngle( self: Self, frictionAngle: float ) -> None: + def setFrictionAngle( self: Self, frictionAngle: float ) -> None: """Set the friction angle. Args: - frictionAngle (float): friction angle (rad) + frictionAngle (float): Friction angle (rad) """ self.m_frictionAngle = frictionAngle - self.Modified() def getOutputType( self: Self ) -> str: """Get output object type. Returns: - str: type of output object. + str: Type of output object. """ - output: vtkDataSet = self.GetOutputDataObject( 0 ) - assert output is not None, "Output is null." - return output.GetClassName() - - def resetDefaultValues( self: Self ) -> None: - """Reset filter parameters to the default values.""" - self.m_computeAdvancedOutputs = False - self.m_grainBulkModulus = DEFAULT_GRAIN_BULK_MODULUS - self.m_specificDensity = WATER_DENSITY - self.m_rockCohesion = DEFAULT_ROCK_COHESION - self.m_frictionAngle = DEFAULT_FRICTION_ANGLE_RAD - - self.m_elasticModuliComputed = False - self.m_biotCoefficientComputed = False - self.m_compressibilityComputed = False - self.m_effectiveStressComputed = False - self.m_totalStressComputed = False - self.m_effectiveStressRatioOedComputed = False - self.m_ready = False - self.Modified() - - def initFilter( self: Self ) -> None: - """Check that mandatory attributes are present in the data set. - - Determine if attributes are on cells or on Points. - Set self.m_ready = True if all data is ok, False otherwise - """ - # check attributes are on cells, or on points otherwise - attributeOnPoints: bool = False - attributeOnCells: bool = self.checkMandatoryAttributes( False ) - if not attributeOnCells: - attributeOnPoints = self.checkMandatoryAttributes( True ) - - self.m_ready = attributeOnPoints or attributeOnCells - self.m_attributeOnPoints = attributeOnPoints + return self.output.GetClassName() - def checkMandatoryAttributes( self: Self, onPoints: bool ) -> bool: + def checkMandatoryAttributes( self: Self ) -> bool: """Check that mandatory attributes are present in the mesh. The mesh must contains either the young Modulus and Poisson's ratio (m_computeYoungPoisson=False) or the bulk and shear moduli (m_computeYoungPoisson=True) - Args: - onPoints (bool): attributes are on points (True) or on cells (False) - Returns: bool: True if all needed attributes are present, False otherwise """ - youngModulusAttributeName: str = ( PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName ) - poissonRatioAttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO.attributeName ) - bulkModulusAttributeName: str = GeosMeshOutputsEnum.BULK_MODULUS.attributeName - shearModulusAttributeName: str = GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName - effectiveStressAttributeName: str = ( GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName ) + self.youngModulusAttributeName: str = PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName + self.poissonRatioAttributeName: str = PostProcessingOutputsEnum.POISSON_RATIO.attributeName + self.youngModulusOnPoints: bool = PostProcessingOutputsEnum.YOUNG_MODULUS.isOnPoints + self.poissonRatioOnPoints: bool = PostProcessingOutputsEnum.POISSON_RATIO.isOnPoints - self.m_computeYoungPoisson = not isAttributeInObject( self.m_output, youngModulusAttributeName, - onPoints ) or not isAttributeInObject( - self.m_output, poissonRatioAttributeName, onPoints ) + self.m_computeYoungPoisson = not isAttributeInObject( self.output, self.youngModulusAttributeName, self.youngModulusOnPoints ) \ + or not isAttributeInObject( self.output, self.poissonRatioAttributeName, self.poissonRatioOnPoints ) # if none of elastic moduli is present, return False - if self.m_computeYoungPoisson and ( - not isAttributeInObject( self.m_output, bulkModulusAttributeName, onPoints ) - or not isAttributeInObject( self.m_output, shearModulusAttributeName, onPoints ) ): - return False + if self.m_computeYoungPoisson: + self.bulkModulusAttributeName: str = GeosMeshOutputsEnum.BULK_MODULUS.attributeName + self.shearModulusAttributeName: str = GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName + self.bulkModulusOnPoints: str = GeosMeshOutputsEnum.BULK_MODULUS.isOnPoints + self.shearModulusOnPoints: str = GeosMeshOutputsEnum.SHEAR_MODULUS.isOnPoints + + if not isAttributeInObject( self.output, self.bulkModulusAttributeName, self.bulkModulusOnPoints ) \ + or not isAttributeInObject( self.output, self.shearModulusAttributeName, self.shearModulusOnPoints ): + return False + + self.effectiveStressAttributeName: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName + self.effectiveStressOnPoints: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.isOnPoints # check effective Stress is present - ret: bool = isAttributeInObject( self.m_output, effectiveStressAttributeName, onPoints ) - return ret + isAllGood: bool = isAttributeInObject( self.output, self.effectiveStressAttributeName, self.effectiveStressOnPoints ) + return isAllGood def computeBasicOutputs( self: Self ) -> bool: """Compute basic geomechanical outputs. @@ -376,76 +273,62 @@ def computeBasicOutputs( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - if not self.m_ready: + self.m_elasticModuliComputed = self.computeElasticModulus() + if not self.m_elasticModuliComputed: + mess: str = ( "Geomechanical outputs cannot be computed without elastic moduli." ) + self.m_logger.error( mess ) return False - try: - self.m_elasticModuliComputed = self.computeElasticModulus() - if not self.m_elasticModuliComputed: - mess: str = ( "Geomechanical outputs cannot be computed without elastic moduli." ) - self.m_logger.error( mess ) - return False - - self.m_biotCoefficientComputed = self.computeBiotCoefficient() - if not self.m_biotCoefficientComputed: - mess2: str = ( "Total stress, elastic strain, and advanced geomechanical " + - "outputs cannot be computed without Biot coefficient." ) - self.m_logger.warning( mess2 ) + self.m_biotCoefficientComputed = self.computeBiotCoefficient() + if not self.m_biotCoefficientComputed: + mess2: str = ( "Total stress, elastic strain, and advanced geomechanical " + + "outputs cannot be computed without Biot coefficient." ) + self.m_logger.warning( mess2 ) - self.m_compressibilityComputed = self.computeCompressibilityCoefficient() + self.m_compressibilityComputed = self.computeCompressibilityCoefficient() - self.m_effectiveStressComputed = self.computeRealEffectiveStressRatio() - if not self.m_effectiveStressComputed: - mess3: str = ( "Total stress, elastic strain, and advanced geomechanical " + - "outputs cannot be computed without effective stress." ) - self.m_logger.warning( mess3 ) + self.m_effectiveStressComputed = self.computeRealEffectiveStressRatio() + if not self.m_effectiveStressComputed: + mess3: str = ( "Total stress, elastic strain, and advanced geomechanical " + + "outputs cannot be computed without effective stress." ) + self.m_logger.warning( mess3 ) - specificGravityComputed: bool = self.computeSpecificGravity() + specificGravityComputed: bool = self.computeSpecificGravity() - # TODO: deactivate lithostatic stress calculation until right formula - litostaticStressComputed: bool = True # self.computeLitostaticStress() + # TODO: deactivate lithostatic stress calculation until right formula + lithostaticStressComputed: bool = True # self.computeLithostaticStress() - elasticStrainComputed: bool = False - if self.m_effectiveStressComputed: - if self.m_biotCoefficientComputed: - self.m_totalStressComputed = self.computeTotalStresses() - if self.m_elasticModuliComputed: - elasticStrainComputed = self.computeElasticStrain() - - reservoirStressPathOedComputed: bool = False + elasticStrainComputed: bool = False + if self.m_effectiveStressComputed: + if self.m_biotCoefficientComputed: + self.m_totalStressComputed = self.computeTotalStresses() if self.m_elasticModuliComputed: - # oedometric DRSP (effective stress ratio in oedometric conditions) - self.m_effectiveStressRatioOedComputed = ( self.computeEffectiveStressRatioOed() ) - - if self.m_biotCoefficientComputed: - reservoirStressPathOedComputed = ( self.computeReservoirStressPathOed() ) - - reservoirStressPathRealComputed: bool = False - if self.m_totalStressComputed: - reservoirStressPathRealComputed = self.computeReservoirStressPathReal() - - if ( self.m_elasticModuliComputed and self.m_biotCoefficientComputed and self.m_compressibilityComputed - and self.m_effectiveStressComputed and specificGravityComputed and elasticStrainComputed - and litostaticStressComputed and self.m_totalStressComputed and self.m_effectiveStressRatioOedComputed - and reservoirStressPathRealComputed and reservoirStressPathRealComputed - and reservoirStressPathOedComputed and reservoirStressPathRealComputed ): - mess4: str = ( "All geomechanical basic outputs were successfully computed." ) - self.m_logger.info( mess4 ) - else: - mess5: str = "Some geomechanical basic outputs were not computed." - self.m_logger.warning( mess5 ) - - except AssertionError as e: - mess6: str = ( "Some of the geomechanical basic outputs were " + "not computed due to:" ) - self.m_logger.error( mess6 ) - self.m_logger.error( str( e ) ) - return False - except Exception as e: - mess7: str = ( "Some of the geomechanical basic outputs were " + "not computed due to:" ) - self.m_logger.critical( mess7 ) - self.m_logger.critical( e, exc_info=True ) + elasticStrainComputed = self.computeElasticStrain() + + reservoirStressPathOedComputed: bool = False + if self.m_elasticModuliComputed: + # oedometric DRSP (effective stress ratio in oedometric conditions) + self.m_effectiveStressRatioOedComputed = ( self.computeEffectiveStressRatioOed() ) + + if self.m_biotCoefficientComputed: + reservoirStressPathOedComputed = ( self.computeReservoirStressPathOed() ) + + reservoirStressPathRealComputed: bool = False + if self.m_totalStressComputed: + reservoirStressPathRealComputed = self.computeReservoirStressPathReal() + + if ( self.m_elasticModuliComputed and self.m_biotCoefficientComputed and self.m_compressibilityComputed + and self.m_effectiveStressComputed and specificGravityComputed and elasticStrainComputed + and lithostaticStressComputed and self.m_totalStressComputed and self.m_effectiveStressRatioOedComputed + and reservoirStressPathRealComputed and reservoirStressPathRealComputed + and reservoirStressPathOedComputed and reservoirStressPathRealComputed ): + mess4: str = ( "All geomechanical basic outputs were successfully computed." ) + self.m_logger.info( mess4 ) + return True + else: + mess5: str = "Some geomechanical basic outputs were not computed." + self.m_logger.error( mess5 ) return False - return True def computeAdvancedOutputs( self: Self ) -> bool: """Compute advanced geomechanical outputs. @@ -453,34 +336,20 @@ def computeAdvancedOutputs( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - if not self.m_ready: - return False - - try: - fractureIndexesComputed: bool = False - criticalPorePressure: bool = False - if self.m_totalStressComputed: - fractureIndexesComputed = self.computeCriticalTotalStressRatio() - criticalPorePressure = self.computeCriticalPorePressure() - - if ( self.m_effectiveStressRatioOedComputed and fractureIndexesComputed and criticalPorePressure ): - mess: str = ( "All geomechanical advanced outputs were " + "successfully computed." ) - self.m_logger.info( mess ) - else: - mess0: str = ( "Some geomechanical advanced outputs were " + "not computed." ) - self.m_logger.warning( mess0 ) - - except AssertionError as e: - mess1: str = ( "Some of the geomechanical basic outputs were " + "not computed due to:" ) - self.m_logger.error( mess1 ) - self.m_logger.error( str( e ) ) - return False - except Exception as e: - mess2: str = ( "Some of the geomechanical advanced outputs " + "were not computed due to:" ) - self.m_logger.critical( mess2 ) - self.m_logger.critical( e, exc_info=True ) + fractureIndexesComputed: bool = False + criticalPorePressure: bool = False + if self.m_totalStressComputed: + fractureIndexesComputed = self.computeCriticalTotalStressRatio() + criticalPorePressure = self.computeCriticalPorePressure() + + if ( self.m_effectiveStressRatioOedComputed and fractureIndexesComputed and criticalPorePressure ): + mess: str = ( "All geomechanical advanced outputs were " + "successfully computed." ) + self.m_logger.info( mess ) + return True + else: + mess0: str = ( "Some geomechanical advanced outputs were " + "not computed." ) + self.m_logger.error( mess0 ) return False - return True def computeElasticModulus( self: Self ) -> bool: """Compute elastic moduli. @@ -502,20 +371,15 @@ def computeElasticModulusFromBulkShear( self: Self ) -> bool: Returns: bool: True if calculation successfully ended, False otherwise """ - youngModulusAttributeName: str = ( PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName ) - poissonRatioAttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO.attributeName ) - bulkModulusAttributeName: str = GeosMeshOutputsEnum.BULK_MODULUS.attributeName - shearModulusAttributeName: str = GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName - ret: bool = True - bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, bulkModulusAttributeName, - self.m_attributeOnPoints ) + bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, self.bulkModulusAttributeName, + self.bulkModulusOnPoints ) - shearModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, shearModulusAttributeName, - self.m_attributeOnPoints ) + shearModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, self.shearModulusAttributeName, + self.shearModulusOnPoints ) try: - assert bulkModulus is not None, ( f"{bulkModulusAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert shearModulus is not None, ( f"{shearModulusAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) + assert bulkModulus is not None, ( f"{self.bulkModulusAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) + assert shearModulus is not None, ( f"{self.shearModulusAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) except AssertionError as e: self.m_logger.error( "Elastic moduli were not computed due to:" ) self.m_logger.error( str( e ) ) @@ -526,11 +390,11 @@ def computeElasticModulusFromBulkShear( self: Self ) -> bool: # assert np.any(youngModulus < 0), ("Young modulus yields negative " + # "values. Check Bulk and Shear modulus values.") createAttribute( - self.m_output, + self.output, youngModulus, - youngModulusAttributeName, + self.youngModulusAttributeName, (), - self.m_attributeOnPoints, + self.youngModulusOnPoints, ) except AssertionError as e: self.m_logger.error( "Young modulus was not computed due to:" ) @@ -542,9 +406,9 @@ def computeElasticModulusFromBulkShear( self: Self ) -> bool: # assert np.any(poissonRatio < 0), ("Poisson ratio yields negative " + # "values. Check Bulk and Shear modulus values.") createAttribute( - self.m_output, + self.output, poissonRatio, - poissonRatioAttributeName, + self.poissonRatioAttributeName, (), self.m_attributeOnPoints, ) @@ -559,16 +423,16 @@ def computeElasticModulusFromYoungPoisson( self: Self ) -> bool: """Compute bulk modulus from Young Modulus and Poisson's ratio. Returns: - bool: True if bulk modulus was wuccessfully computed, False otherwise + bool: True if bulk modulus was successfully computed, False otherwise """ try: youngModulusAttributeName: str = ( PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName ) poissonRatioAttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO.attributeName ) bulkModulusAttributeName: str = ( GeosMeshOutputsEnum.BULK_MODULUS.attributeName ) - if not isAttributeInObject( self.m_output, bulkModulusAttributeName, self.m_attributeOnPoints ): - youngModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, youngModulusAttributeName, + if not isAttributeInObject( self.output, bulkModulusAttributeName, self.m_attributeOnPoints ): + youngModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, youngModulusAttributeName, self.m_attributeOnPoints ) - poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, poissonRatioAttributeName, + poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioAttributeName, self.m_attributeOnPoints ) assert youngModulus is not None, ( f"{youngModulusAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) @@ -578,7 +442,7 @@ def computeElasticModulusFromYoungPoisson( self: Self ) -> bool: # assert np.any(bulkModulus < 0), ("Bulk modulus yields negative " + # "values. Check Young modulus and Poisson ratio values.") ret: bool = createAttribute( - self.m_output, + self.output, bulkModulus, bulkModulusAttributeName, (), @@ -599,9 +463,9 @@ def computeBiotCoefficient( self: Self ) -> bool: bool: True if calculation successfully ended, False otherwise. """ biotCoefficientAttributeName: str = ( PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName ) - if not isAttributeInObject( self.m_output, biotCoefficientAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, biotCoefficientAttributeName, self.m_attributeOnPoints ): bulkModulusAttributeName: str = ( GeosMeshOutputsEnum.BULK_MODULUS.attributeName ) - bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, bulkModulusAttributeName, + bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, bulkModulusAttributeName, self.m_attributeOnPoints ) try: assert bulkModulus is not None, ( f"{bulkModulusAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) @@ -609,7 +473,7 @@ def computeBiotCoefficient( self: Self ) -> bool: biotCoefficient: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.m_grainBulkModulus, bulkModulus ) createAttribute( - self.m_output, + self.output, biotCoefficient, biotCoefficientAttributeName, (), @@ -633,20 +497,20 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: """ compressibilityAttributeName: str = ( PostProcessingOutputsEnum.COMPRESSIBILITY.attributeName ) bulkModulusAttributeName: str = GeosMeshOutputsEnum.BULK_MODULUS.attributeName - bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, bulkModulusAttributeName, + bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, bulkModulusAttributeName, self.m_attributeOnPoints ) porosityAttributeName: str = GeosMeshOutputsEnum.POROSITY.attributeName - porosity: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, porosityAttributeName, + porosity: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityAttributeName, self.m_attributeOnPoints ) porosityInitialAttributeName: str = ( GeosMeshOutputsEnum.POROSITY_INI.attributeName ) - porosityInitial: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, porosityInitialAttributeName, + porosityInitial: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityInitialAttributeName, self.m_attributeOnPoints ) - if not isAttributeInObject( self.m_output, compressibilityAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, compressibilityAttributeName, self.m_attributeOnPoints ): poissonRatioAttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO.attributeName ) - poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, poissonRatioAttributeName, + poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioAttributeName, self.m_attributeOnPoints ) biotCoefficientAttributeName: str = ( PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName ) - biotCoefficient: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, biotCoefficientAttributeName, + biotCoefficient: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, biotCoefficientAttributeName, self.m_attributeOnPoints ) try: @@ -658,7 +522,7 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: compressibility: npt.NDArray[ np.float64 ] = fcts.compressibility( poissonRatio, bulkModulus, biotCoefficient, porosity ) createAttribute( - self.m_output, + self.output, compressibility, compressibilityAttributeName, (), @@ -671,9 +535,9 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: # oedometric compressibility compressibilityOedAttributeName: str = ( PostProcessingOutputsEnum.COMPRESSIBILITY_OED.attributeName ) - if not isAttributeInObject( self.m_output, compressibilityOedAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, compressibilityOedAttributeName, self.m_attributeOnPoints ): shearModulusAttributeName: str = ( GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName ) - shearModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, shearModulusAttributeName, + shearModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, shearModulusAttributeName, self.m_attributeOnPoints ) try: assert poissonRatio is not None, ( f"{poissonRatioAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) @@ -682,7 +546,7 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: compressibilityOed: npt.NDArray[ np.float64 ] = fcts.compressibilityOed( bulkModulus, shearModulus, porosityInitial ) createAttribute( - self.m_output, + self.output, compressibilityOed, compressibilityOedAttributeName, (), @@ -695,9 +559,9 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: # real compressibility compressibilityRealAttributeName: str = ( PostProcessingOutputsEnum.COMPRESSIBILITY_REAL.attributeName ) - if not isAttributeInObject( self.m_output, compressibilityRealAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, compressibilityRealAttributeName, self.m_attributeOnPoints ): deltaPressureAttributeName: str = ( GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName ) - deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, deltaPressureAttributeName, + deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, self.m_attributeOnPoints ) try: assert deltaPressure is not None, ( f"{deltaPressureAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) @@ -706,7 +570,7 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: compressibilityReal: npt.NDArray[ np.float64 ] = fcts.compressibilityReal( deltaPressure, porosity, porosityInitial ) createAttribute( - self.m_output, + self.output, compressibilityReal, compressibilityRealAttributeName, (), @@ -729,16 +593,16 @@ def computeSpecificGravity( self: Self ) -> bool: bool: True if the attribute is correctly created, False otherwise. """ densityAttributeName: str = GeosMeshOutputsEnum.ROCK_DENSITY.attributeName - density: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, densityAttributeName, + density: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, densityAttributeName, self.m_attributeOnPoints ) specificGravityAttributeName: str = ( PostProcessingOutputsEnum.SPECIFIC_GRAVITY.attributeName ) - if not isAttributeInObject( self.m_output, specificGravityAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, specificGravityAttributeName, self.m_attributeOnPoints ): try: assert density is not None, ( f"{densityAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) specificGravity: npt.NDArray[ np.float64 ] = fcts.specificGravity( density, self.m_specificDensity ) createAttribute( - self.m_output, + self.output, specificGravity, specificGravityAttributeName, (), @@ -759,7 +623,7 @@ def computeRealEffectiveStressRatio( self: Self ) -> bool: """ # test if effective stress is in the table if not isAttributeInObject( - self.m_output, + self.output, GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName, self.m_attributeOnPoints, ): @@ -783,29 +647,29 @@ def computeTotalStresses( self: Self ) -> bool: """ # compute total stress at initial time step totalStressT0AttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.attributeName ) - if not isAttributeInObject( self.m_output, totalStressT0AttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, totalStressT0AttributeName, self.m_attributeOnPoints ): self.computeTotalStressInitial() # compute total stress at current time step totalStressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL.attributeName ) - if not isAttributeInObject( self.m_output, totalStressAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, totalStressAttributeName, self.m_attributeOnPoints ): try: effectiveStressAttributeName: str = ( GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName ) effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( - self.m_output, + self.output, effectiveStressAttributeName, self.m_attributeOnPoints, ) assert effectiveStress is not None, ( f"{effectiveStressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName - pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, pressureAttributeName, + pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, self.m_attributeOnPoints ) assert pressure is not None, ( f"{pressureAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) biotCoefficientAttributeName: str = ( PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName ) biotCoefficient: npt.NDArray[ np.float64 ] = getArrayInObject( - self.m_output, + self.output, biotCoefficientAttributeName, self.m_attributeOnPoints, ) @@ -839,18 +703,18 @@ def computeTotalStressInitial( self: Self ) -> bool: youngModulusT0AttributeName: str = ( PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.attributeName ) poissonRatioT0AttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.attributeName ) # get bulk modulus at initial time step - if isAttributeInObject( self.m_output, bulkModulusT0AttributeName, self.m_attributeOnPoints ): + if isAttributeInObject( self.output, bulkModulusT0AttributeName, self.m_attributeOnPoints ): - bulkModulusT0 = getArrayInObject( self.m_output, bulkModulusT0AttributeName, self.m_attributeOnPoints ) + bulkModulusT0 = getArrayInObject( self.output, bulkModulusT0AttributeName, self.m_attributeOnPoints ) # or compute it from Young and Poisson if not an attribute - elif isAttributeInObject( self.m_output, youngModulusT0AttributeName, + elif isAttributeInObject( self.output, youngModulusT0AttributeName, self.m_attributeOnPoints ) and isAttributeInObject( - self.m_output, poissonRatioT0AttributeName, self.m_attributeOnPoints ): + self.output, poissonRatioT0AttributeName, self.m_attributeOnPoints ): - youngModulusT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, + youngModulusT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, youngModulusT0AttributeName, self.m_attributeOnPoints ) - poissonRatioT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, + poissonRatioT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioT0AttributeName, self.m_attributeOnPoints ) assert poissonRatioT0 is not None, "Poisson's ratio is undefined." @@ -869,13 +733,13 @@ def computeTotalStressInitial( self: Self ) -> bool: # compute total stress at initial time step # get effective stress attribute effectiveStressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.attributeName ) - effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, effectiveStressAttributeName, + effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressAttributeName, self.m_attributeOnPoints ) assert effectiveStress is not None, ( f"{effectiveStressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) # get pressure attribute pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName - pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, pressureAttributeName, + pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, self.m_attributeOnPoints ) pressureT0: npt.NDArray[ np.float64 ] # case pressureT0 is None, total stress = effective stress @@ -883,7 +747,7 @@ def computeTotalStressInitial( self: Self ) -> bool: if pressure is not None: # get delta pressure to recompute pressure at initial time step (pressureTo) deltaPressureAttributeName: str = ( GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName ) - deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, deltaPressureAttributeName, + deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, self.m_attributeOnPoints ) assert deltaPressure is not None, ( f"{deltaPressureAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) pressureT0 = pressure - deltaPressure @@ -939,7 +803,7 @@ def doComputeTotalStress( # create total stress attribute assert totalStress is not None, "Total stress data is null." createAttribute( - self.m_output, + self.output, totalStress, totalStressAttributeName, ComponentNameEnum.XYZ.value, @@ -947,17 +811,17 @@ def doComputeTotalStress( ) return True - def computeLitostaticStress( self: Self ) -> bool: + def computeLithostaticStress( self: Self ) -> bool: """Compute lithostatic stress. Returns: bool: True if calculation successfully ended, False otherwise. """ - litostaticStressAttributeName: str = ( PostProcessingOutputsEnum.LITHOSTATIC_STRESS.attributeName ) - if not isAttributeInObject( self.m_output, litostaticStressAttributeName, self.m_attributeOnPoints ): + lithostaticStressAttributeName: str = ( PostProcessingOutputsEnum.LITHOSTATIC_STRESS.attributeName ) + if not isAttributeInObject( self.output, lithostaticStressAttributeName, self.m_attributeOnPoints ): densityAttributeName: str = GeosMeshOutputsEnum.ROCK_DENSITY.attributeName - density: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, densityAttributeName, + density: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, densityAttributeName, self.m_attributeOnPoints ) try: depth: npt.NDArray[ np.float64 ] = self.computeDepthAlongLine( @@ -965,11 +829,11 @@ def computeLitostaticStress( self: Self ) -> bool: assert depth is not None, "Depth is undefined." assert density is not None, ( f"{densityAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - litostaticStress = fcts.lithostaticStress( depth, density, GRAVITY ) + lithostaticStress = fcts.lithostaticStress( depth, density, GRAVITY ) createAttribute( - self.m_output, - litostaticStress, - litostaticStressAttributeName, + self.output, + lithostaticStress, + lithostaticStressAttributeName, (), self.m_attributeOnPoints, ) @@ -1010,7 +874,7 @@ def computeDepthInMesh( self: Self ) -> npt.NDArray[ np.float64 ]: return depth def getZcoordinates( self: Self ) -> npt.NDArray[ np.float64 ]: - """Get z coordinates from self.m_output. + """Get z coordinates from self.output. Returns: npt.NDArray[np.float64]: 1D array with depth property @@ -1030,22 +894,22 @@ def computeElasticStrain( self: Self ) -> bool: bool: return True if calculation successfully ended, False otherwise. """ elasticStrainAttributeName: str = ( PostProcessingOutputsEnum.STRAIN_ELASTIC.attributeName ) - if not isAttributeInObject( self.m_output, elasticStrainAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, elasticStrainAttributeName, self.m_attributeOnPoints ): effectiveStressAttributeName: str = ( GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName ) - effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, effectiveStressAttributeName, + effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressAttributeName, self.m_attributeOnPoints ) effectiveStressIniAttributeName: str = ( PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.attributeName ) - effectiveStressIni: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, + effectiveStressIni: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressIniAttributeName, self.m_attributeOnPoints ) bulkModulusAttributeName: str = ( GeosMeshOutputsEnum.BULK_MODULUS.attributeName ) - bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, bulkModulusAttributeName, + bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, bulkModulusAttributeName, self.m_attributeOnPoints ) shearModulusAttributeName: str = ( GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName ) - shearModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, shearModulusAttributeName, + shearModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, shearModulusAttributeName, self.m_attributeOnPoints ) try: @@ -1059,7 +923,7 @@ def computeElasticStrain( self: Self ) -> bool: elasticStrain: npt.NDArray[ np.float64 ] = ( fcts.elasticStrainFromBulkShear( deltaEffectiveStress, bulkModulus, shearModulus ) ) createAttribute( - self.m_output, + self.output, elasticStrain, elasticStrainAttributeName, ComponentNameEnum.XYZ.value, @@ -1079,20 +943,20 @@ def computeReservoirStressPathReal( self: Self ) -> bool: bool: return True if calculation successfully ended, False otherwise. """ RSPrealAttributeName: str = PostProcessingOutputsEnum.RSP_REAL.attributeName - if not isAttributeInObject( self.m_output, RSPrealAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, RSPrealAttributeName, self.m_attributeOnPoints ): # real RSP try: # total stress at current and initial time steps totalStressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL.attributeName ) - totalStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, totalStressAttributeName, + totalStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, totalStressAttributeName, self.m_attributeOnPoints ) totalStressT0AttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.attributeName ) - totalStressIni: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, totalStressT0AttributeName, + totalStressIni: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, totalStressT0AttributeName, self.m_attributeOnPoints ) deltaPressureAttributeName: str = ( GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName ) - deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, deltaPressureAttributeName, + deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, self.m_attributeOnPoints ) assert totalStress is not None, ( f"{totalStressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) @@ -1102,11 +966,11 @@ def computeReservoirStressPathReal( self: Self ) -> bool: # create delta stress attribute for QC deltaTotalStressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.attributeName ) deltaStress: npt.NDArray[ np.float64 ] = totalStress - totalStressIni - componentNames: tuple[ str, ...] = getComponentNames( self.m_output, totalStressAttributeName, + componentNames: tuple[ str, ...] = getComponentNames( self.output, totalStressAttributeName, self.m_attributeOnPoints ) assert deltaStress is not None, ( f"{deltaTotalStressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) createAttribute( - self.m_output, + self.output, deltaStress, deltaTotalStressAttributeName, componentNames, @@ -1116,7 +980,7 @@ def computeReservoirStressPathReal( self: Self ) -> bool: RSPreal: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathReal( deltaStress, deltaPressure ) assert RSPreal is not None, ( f"{RSPrealAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) createAttribute( - self.m_output, + self.output, RSPreal, RSPrealAttributeName, componentNames, @@ -1136,14 +1000,14 @@ def computeReservoirStressPathOed( self: Self ) -> bool: bool: return True if calculation successfully ended, False otherwise. """ RSPOedAttributeName: str = PostProcessingOutputsEnum.RSP_OED.attributeName - if not isAttributeInObject( self.m_output, RSPOedAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, RSPOedAttributeName, self.m_attributeOnPoints ): poissonRatioAttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO.attributeName ) - poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, poissonRatioAttributeName, + poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioAttributeName, self.m_attributeOnPoints ) biotCoefficientAttributeName: str = ( PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName ) - biotCoefficient: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, biotCoefficientAttributeName, + biotCoefficient: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, biotCoefficientAttributeName, self.m_attributeOnPoints ) try: @@ -1152,7 +1016,7 @@ def computeReservoirStressPathOed( self: Self ) -> bool: RSPoed: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathOed( biotCoefficient, poissonRatio ) createAttribute( - self.m_output, + self.output, RSPoed, RSPOedAttributeName, (), @@ -1172,10 +1036,10 @@ def computeStressRatioReal( self: Self, inputAttribute: AttributeEnum, outputAtt bool: return True if calculation successfully ended, False otherwise. """ stressRatioRealAttributeName = outputAttribute.attributeName - if not isAttributeInObject( self.m_output, stressRatioRealAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, stressRatioRealAttributeName, self.m_attributeOnPoints ): try: stressAttributeName: str = inputAttribute.attributeName - stress: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, stressAttributeName, + stress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, stressAttributeName, self.m_attributeOnPoints ) assert stress is not None, ( f"{stressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) @@ -1185,7 +1049,7 @@ def computeStressRatioReal( self: Self, inputAttribute: AttributeEnum, outputAtt stressRatioReal: npt.NDArray[ np.float64 ] = fcts.stressRatio( horizontalStress, verticalStress ) createAttribute( - self.m_output, + self.output, stressRatioReal, stressRatioRealAttributeName, (), @@ -1207,12 +1071,12 @@ def computeEffectiveStressRatioOed( self: Self ) -> bool: effectiveStressRatioOedAttributeName: str = ( PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_OED.attributeName ) if not isAttributeInObject( - self.m_output, + self.output, effectiveStressRatioOedAttributeName, self.m_attributeOnPoints, ): poissonRatioAttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO.attributeName ) - poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, poissonRatioAttributeName, + poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioAttributeName, self.m_attributeOnPoints ) try: @@ -1220,7 +1084,7 @@ def computeEffectiveStressRatioOed( self: Self ) -> bool: effectiveStressRatioOed: npt.NDArray[ np.float64 ] = ( fcts.deviatoricStressPathOed( poissonRatio ) ) createAttribute( - self.m_output, + self.output, effectiveStressRatioOed, effectiveStressRatioOedAttributeName, (), @@ -1241,14 +1105,14 @@ def computeCriticalTotalStressRatio( self: Self ) -> bool: ret: bool = True fractureIndexAttributeName = ( PostProcessingOutputsEnum.CRITICAL_TOTAL_STRESS_RATIO.attributeName ) - if not isAttributeInObject( self.m_output, fractureIndexAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, fractureIndexAttributeName, self.m_attributeOnPoints ): stressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL.attributeName ) - stress: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, stressAttributeName, + stress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, stressAttributeName, self.m_attributeOnPoints ) pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName - pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, pressureAttributeName, + pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, self.m_attributeOnPoints ) try: @@ -1266,7 +1130,7 @@ def computeCriticalTotalStressRatio( self: Self ) -> bool: criticalTotalStressRatio: npt.NDArray[ np.float64 ] = ( fcts.criticalTotalStressRatio( pressure, verticalStress ) ) createAttribute( - self.m_output, + self.output, criticalTotalStressRatio, fractureIndexAttributeName, (), @@ -1288,7 +1152,7 @@ def computeCriticalTotalStressRatio( self: Self ) -> bool: fractureThresholdAttributeName = ( PostProcessingOutputsEnum.TOTAL_STRESS_RATIO_THRESHOLD.attributeName ) createAttribute( - self.m_output, + self.output, stressRatioThreshold, fractureThresholdAttributeName, (), @@ -1309,14 +1173,14 @@ def computeCriticalPorePressure( self: Self ) -> bool: """ ret: bool = True criticalPorePressureAttributeName = ( PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE.attributeName ) - if not isAttributeInObject( self.m_output, criticalPorePressureAttributeName, self.m_attributeOnPoints ): + if not isAttributeInObject( self.output, criticalPorePressureAttributeName, self.m_attributeOnPoints ): stressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL.attributeName ) - stress: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, stressAttributeName, + stress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, stressAttributeName, self.m_attributeOnPoints ) pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName - pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, pressureAttributeName, + pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, self.m_attributeOnPoints ) try: @@ -1334,7 +1198,7 @@ def computeCriticalPorePressure( self: Self ) -> bool: criticalPorePressure: npt.NDArray[ np.float64 ] = ( fcts.criticalPorePressure( -1.0 * stress, self.m_rockCohesion, self.m_frictionAngle ) ) createAttribute( - self.m_output, + self.output, criticalPorePressure, criticalPorePressureAttributeName, (), @@ -1353,7 +1217,7 @@ def computeCriticalPorePressure( self: Self ) -> bool: criticalPorePressureIndexAttributeName = ( PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName ) createAttribute( - self.m_output, + self.output, criticalPorePressureIndex, criticalPorePressureIndexAttributeName, (), @@ -1373,10 +1237,10 @@ def getPointCoordinates( self: Self ) -> npt.NDArray[ np.float64 ]: npt.NDArray[np.float64]: points/cell center coordinates """ if self.m_attributeOnPoints: - return self.m_output.GetPoints() # type: ignore[no-any-return] + return self.output.GetPoints() # type: ignore[no-any-return] else: # Find cell centers filter = vtkCellCenters() - filter.SetInputDataObject( self.m_output ) + filter.SetInputDataObject( self.output ) filter.Update() return filter.GetOutput().GetPoints() # type: ignore[no-any-return] From 9ef68145bf948e13169a88b4253b6d3909779704 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Mon, 22 Sep 2025 14:45:56 +0200 Subject: [PATCH 05/40] refactor computeElasticModulus functions --- .../mesh/processing/GeomechanicsCalculator.py | 219 ++++++++---------- 1 file changed, 93 insertions(+), 126 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index fbe03afe5..8d508c088 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -125,18 +125,6 @@ def __init__( self.m_rockCohesion: float = DEFAULT_ROCK_COHESION self.m_frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD - # computation results - self.m_elasticModuliComputed: bool = False - self.m_biotCoefficientComputed: bool = False - self.m_compressibilityComputed: bool = False - self.m_effectiveStressComputed: bool = False - self.m_totalStressComputed: bool = False - self.m_effectiveStressRatioOedComputed: bool = False - - # will compute results if m_ready is True (updated by initFilter method) - self.m_ready: bool = False - # attributes are either on points or on cells - self.m_attributeOnPoints: bool = False # elastic moduli are either bulk and Shear moduli (m_computeYoungPoisson=True) # or young Modulus and poisson's ratio (m_computeYoungPoisson=False) self.m_computeYoungPoisson: bool = True @@ -273,63 +261,55 @@ def computeBasicOutputs( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - self.m_elasticModuliComputed = self.computeElasticModulus() - if not self.m_elasticModuliComputed: - mess: str = ( "Geomechanical outputs cannot be computed without elastic moduli." ) - self.m_logger.error( mess ) + if not self.computeElasticModulus(): + self.m_logger.error( "Elastic modulus computation failed." ) return False - self.m_biotCoefficientComputed = self.computeBiotCoefficient() - if not self.m_biotCoefficientComputed: - mess2: str = ( "Total stress, elastic strain, and advanced geomechanical " + - "outputs cannot be computed without Biot coefficient." ) - self.m_logger.warning( mess2 ) + if not self.computeBiotCoefficient(): + self.m_logger.error( "Biot coefficient computation failed." ) + return False - self.m_compressibilityComputed = self.computeCompressibilityCoefficient() + if not self.computeCompressibilityCoefficient(): + self.m_logger.error( "Compressibility coefficient computation failed." ) + return False - self.m_effectiveStressComputed = self.computeRealEffectiveStressRatio() - if not self.m_effectiveStressComputed: - mess3: str = ( "Total stress, elastic strain, and advanced geomechanical " + - "outputs cannot be computed without effective stress." ) - self.m_logger.warning( mess3 ) + if not self.computeRealEffectiveStressRatio(): + self.m_logger.error( "Effective stress ratio computation failed." ) + return False - specificGravityComputed: bool = self.computeSpecificGravity() + if not self.computeSpecificGravity(): + self.m_logger.error( "Specific gravity computation failed.") + return False # TODO: deactivate lithostatic stress calculation until right formula - lithostaticStressComputed: bool = True # self.computeLithostaticStress() + # if not self.computeLithostaticStress(): + # self.m_logger.error( "Lithostatic stress computation failed." ) + # return False - elasticStrainComputed: bool = False - if self.m_effectiveStressComputed: - if self.m_biotCoefficientComputed: - self.m_totalStressComputed = self.computeTotalStresses() - if self.m_elasticModuliComputed: - elasticStrainComputed = self.computeElasticStrain() + if not self.computeTotalStresses(): + self.m_logger.error( "Total stresses computation failed." ) + return False - reservoirStressPathOedComputed: bool = False - if self.m_elasticModuliComputed: - # oedometric DRSP (effective stress ratio in oedometric conditions) - self.m_effectiveStressRatioOedComputed = ( self.computeEffectiveStressRatioOed() ) + if not self.computeElasticStrain(): + self.m_logger.error( "Elastic strain computation failed." ) + return False - if self.m_biotCoefficientComputed: - reservoirStressPathOedComputed = ( self.computeReservoirStressPathOed() ) + # oedometric DRSP (effective stress ratio in oedometric conditions) + if not self.computeEffectiveStressRatioOed(): + self.m_logger.error( "Effective stress ration in oedometric condition computation failed." ) + return False - reservoirStressPathRealComputed: bool = False - if self.m_totalStressComputed: - reservoirStressPathRealComputed = self.computeReservoirStressPathReal() - - if ( self.m_elasticModuliComputed and self.m_biotCoefficientComputed and self.m_compressibilityComputed - and self.m_effectiveStressComputed and specificGravityComputed and elasticStrainComputed - and lithostaticStressComputed and self.m_totalStressComputed and self.m_effectiveStressRatioOedComputed - and reservoirStressPathRealComputed and reservoirStressPathRealComputed - and reservoirStressPathOedComputed and reservoirStressPathRealComputed ): - mess4: str = ( "All geomechanical basic outputs were successfully computed." ) - self.m_logger.info( mess4 ) - return True - else: - mess5: str = "Some geomechanical basic outputs were not computed." - self.m_logger.error( mess5 ) + if not self.computeReservoirStressPathOed(): + self.m_logger.error( "Reservoir stress path in oedometric condition computation failed." ) + return False + + if not self.computeReservoirStressPathReal(): + self.m_logger.error( "Reservoir stress path computation failed." ) return False + self.m_logger.info( "All geomechanical basic outputs were successfully computed." ) + return True + def computeAdvancedOutputs( self: Self ) -> bool: """Compute advanced geomechanical outputs. @@ -361,6 +341,10 @@ def computeElasticModulus( self: Self ) -> bool: bool: True if elastic moduli are already present or if calculation successfully ended, False otherwise. """ + self.bulkModulus: npt.NDArray[ np.float64 ] + self.shearModulus: npt.NDArray[ np.float64 ] + self.youngModulus: npt.NDArray[ np.float64 ] + self.poissonRatio: npt.NDArray[ np.float64 ] if self.m_computeYoungPoisson: return self.computeElasticModulusFromBulkShear() return self.computeElasticModulusFromYoungPoisson() @@ -371,53 +355,36 @@ def computeElasticModulusFromBulkShear( self: Self ) -> bool: Returns: bool: True if calculation successfully ended, False otherwise """ - ret: bool = True - bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, self.bulkModulusAttributeName, - self.bulkModulusOnPoints ) + self.bulkModulus = getArrayInObject( self.output, self.bulkModulusAttributeName, self.bulkModulusOnPoints ) + self.shearModulus = getArrayInObject( self.output, self.shearModulusAttributeName, self.shearModulusOnPoints ) - shearModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, self.shearModulusAttributeName, - self.shearModulusOnPoints ) - try: - assert bulkModulus is not None, ( f"{self.bulkModulusAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert shearModulus is not None, ( f"{self.shearModulusAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) - except AssertionError as e: - self.m_logger.error( "Elastic moduli were not computed due to:" ) - self.m_logger.error( str( e ) ) + self.youngModulus = fcts.youngModulus( self.bulkModulus, self.shearModulus ) + if np.any( self.youngModulus < 0 ): + self.m_logger.error( "Young modulus yields negative values. Check Bulk and Shear modulus values." ) + return False + + if not createAttribute( self.output, + self.youngModulus, + self.youngModulusAttributeName, + onPoints=self.youngModulusOnPoints, + logger=self.m_logger ): + self.m_logger.error( "Young modulus computation failed." ) return False - try: - youngModulus: npt.NDArray[ np.float64 ] = fcts.youngModulus( bulkModulus, shearModulus ) - # assert np.any(youngModulus < 0), ("Young modulus yields negative " + - # "values. Check Bulk and Shear modulus values.") - createAttribute( - self.output, - youngModulus, - self.youngModulusAttributeName, - (), - self.youngModulusOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Young modulus was not computed due to:" ) - self.m_logger.error( str( e ) ) - ret = False + self.poissonRatio = fcts.poissonRatio( self.bulkModulus, self.shearModulus ) + if np.any( self.poissonRatio < 0 ): + self.m_logger.error( "Poisson ratio yields negative values. Check Bulk and Shear modulus values.") + return False - try: - poissonRatio: npt.NDArray[ np.float64 ] = fcts.poissonRatio( bulkModulus, shearModulus ) - # assert np.any(poissonRatio < 0), ("Poisson ratio yields negative " + - # "values. Check Bulk and Shear modulus values.") - createAttribute( - self.output, - poissonRatio, - self.poissonRatioAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Poisson's ratio was not computed due to:" ) - self.m_logger.error( str( e ) ) - ret = False + if not createAttribute( self.output, + self.poissonRatio, + self.poissonRatioAttributeName, + onPoints=self.poissonRatioOnPoints, + logger=self.m_logger ): + self.m_logger.error( "Poisson ration computation failed." ) + return False - return ret + return True def computeElasticModulusFromYoungPoisson( self: Self ) -> bool: """Compute bulk modulus from Young Modulus and Poisson's ratio. @@ -425,35 +392,35 @@ def computeElasticModulusFromYoungPoisson( self: Self ) -> bool: Returns: bool: True if bulk modulus was successfully computed, False otherwise """ - try: - youngModulusAttributeName: str = ( PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName ) - poissonRatioAttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO.attributeName ) - bulkModulusAttributeName: str = ( GeosMeshOutputsEnum.BULK_MODULUS.attributeName ) - if not isAttributeInObject( self.output, bulkModulusAttributeName, self.m_attributeOnPoints ): - youngModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, youngModulusAttributeName, - self.m_attributeOnPoints ) - poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioAttributeName, - self.m_attributeOnPoints ) - - assert youngModulus is not None, ( f"{youngModulusAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert poissonRatio is not None, ( f"{poissonRatioAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - - bulkModulus: npt.NDArray[ np.float64 ] = fcts.bulkModulus( youngModulus, poissonRatio ) - # assert np.any(bulkModulus < 0), ("Bulk modulus yields negative " + - # "values. Check Young modulus and Poisson ratio values.") - ret: bool = createAttribute( - self.output, - bulkModulus, - bulkModulusAttributeName, - (), - self.m_attributeOnPoints, - ) - return ret - - except AssertionError as e: - self.m_logger.error( "Bulk modulus was not computed due to:" ) - self.m_logger.error( str( e ) ) + self.youngModulus = getArrayInObject( self.output, self.youngModulusAttributeName, self.youngModulusOnPoints ) + self.poissonRatio = getArrayInObject( self.output, self.poissonRatioAttributeName, self.poissonRatioOnPoints ) + + self.bulkModulus = fcts.bulkModulus( self.youngModulus, self.poissonRatio ) + if np.any( self.bulkModulus < 0 ): + self.m_logger.error( "Bulk modulus yields negative values. Check Young modulus and Poisson ratio values.") + return False + + if not createAttribute( self.output, + self.bulkModulus, + self.bulkModulusAttributeName, + onPoints=self.bulkModulusOnPoints, + logger=self.m_logger ): + self.m_logger.error( "Bulk modulus computation failed." ) + return False + + self.shearModulus = fcts.shearModulus( self.youngModulus, self.poissonRatio ) + if np.any( self.shearModulus < 0 ): + self.m_logger.error( "Shear modulus yields negative values. Check Young modulus and Poisson ratio values.") + return False + + if not createAttribute( self.output, + self.shearModulus, + self.shearModulusAttributeName, + onPoints=self.shearModulusOnPoints, + logger=self.m_logger ): + self.m_logger.error( "Shear modulus computation failed." ) return False + return True def computeBiotCoefficient( self: Self ) -> bool: From a88a1e74cc9c52969c359a01def454a9ccede91f Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Mon, 22 Sep 2025 14:55:30 +0200 Subject: [PATCH 06/40] refactor compute biot coefficient --- .../mesh/processing/GeomechanicsCalculator.py | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index 8d508c088..1ffc0ce5c 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -429,29 +429,16 @@ def computeBiotCoefficient( self: Self ) -> bool: Returns: bool: True if calculation successfully ended, False otherwise. """ - biotCoefficientAttributeName: str = ( PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName ) - if not isAttributeInObject( self.output, biotCoefficientAttributeName, self.m_attributeOnPoints ): - bulkModulusAttributeName: str = ( GeosMeshOutputsEnum.BULK_MODULUS.attributeName ) - bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, bulkModulusAttributeName, - self.m_attributeOnPoints ) - try: - assert bulkModulus is not None, ( f"{bulkModulusAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) + biotCoefficientAttributeName: str = PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName + biotCoefficientOnPoints: bool = PostProcessingOutputsEnum.BIOT_COEFFICIENT.isOnPoints - biotCoefficient: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.m_grainBulkModulus, - bulkModulus ) - createAttribute( - self.output, - biotCoefficient, - biotCoefficientAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Biot coefficient was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False + biotCoefficient: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.m_grainBulkModulus, self.bulkModulus ) + return createAttribute( self.output, + biotCoefficient, + biotCoefficientAttributeName, + onPoints=biotCoefficientOnPoints, + logger=self.m_logger ) - return True def computeCompressibilityCoefficient( self: Self ) -> bool: """Compute compressibility coefficient from simulation outputs. From ce526f9348061c91fbb6705f4b5aec16766d807e Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Mon, 22 Sep 2025 15:26:11 +0200 Subject: [PATCH 07/40] refactor computeCompressibilityCoefficient function --- .../mesh/processing/GeomechanicsCalculator.py | 128 ++++++------------ 1 file changed, 44 insertions(+), 84 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index 1ffc0ce5c..6268b3eb5 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -270,7 +270,6 @@ def computeBasicOutputs( self: Self ) -> bool: return False if not self.computeCompressibilityCoefficient(): - self.m_logger.error( "Compressibility coefficient computation failed." ) return False if not self.computeRealEffectiveStressRatio(): @@ -432,14 +431,13 @@ def computeBiotCoefficient( self: Self ) -> bool: biotCoefficientAttributeName: str = PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName biotCoefficientOnPoints: bool = PostProcessingOutputsEnum.BIOT_COEFFICIENT.isOnPoints - biotCoefficient: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.m_grainBulkModulus, self.bulkModulus ) + self.biotCoefficient: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.m_grainBulkModulus, self.bulkModulus ) return createAttribute( self.output, - biotCoefficient, + self.biotCoefficient, biotCoefficientAttributeName, onPoints=biotCoefficientOnPoints, logger=self.m_logger ) - def computeCompressibilityCoefficient( self: Self ) -> bool: """Compute compressibility coefficient from simulation outputs. @@ -449,91 +447,53 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: Returns: bool: True if the attribute is correctly created, False otherwise. """ - compressibilityAttributeName: str = ( PostProcessingOutputsEnum.COMPRESSIBILITY.attributeName ) - bulkModulusAttributeName: str = GeosMeshOutputsEnum.BULK_MODULUS.attributeName - bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, bulkModulusAttributeName, - self.m_attributeOnPoints ) porosityAttributeName: str = GeosMeshOutputsEnum.POROSITY.attributeName - porosity: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityAttributeName, - self.m_attributeOnPoints ) - porosityInitialAttributeName: str = ( GeosMeshOutputsEnum.POROSITY_INI.attributeName ) - porosityInitial: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityInitialAttributeName, - self.m_attributeOnPoints ) - if not isAttributeInObject( self.output, compressibilityAttributeName, self.m_attributeOnPoints ): - poissonRatioAttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO.attributeName ) - poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioAttributeName, - self.m_attributeOnPoints ) - biotCoefficientAttributeName: str = ( PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName ) - biotCoefficient: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, biotCoefficientAttributeName, - self.m_attributeOnPoints ) - - try: - assert poissonRatio is not None, ( f"{poissonRatioAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert bulkModulus is not None, ( f"{bulkModulusAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert biotCoefficient is not None, ( f"{biotCoefficientAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert porosity is not None, ( f"{porosityAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) - - compressibility: npt.NDArray[ np.float64 ] = fcts.compressibility( poissonRatio, bulkModulus, - biotCoefficient, porosity ) - createAttribute( - self.output, - compressibility, - compressibilityAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Compressibility was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False + porosityOnPoints: bool = GeosMeshOutputsEnum.POROSITY.isOnPoints + porosity: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityAttributeName, porosityOnPoints ) + + compressibilityAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY.attributeName + compressibilityOnPoints: bool = PostProcessingOutputsEnum.COMPRESSIBILITY.isOnPoints + compressibility: npt.NDArray[ np.float64 ] = fcts.compressibility( self.poissonRatio, self.bulkModulus, + self.biotCoefficient, porosity ) + if not createAttribute( self.output, + compressibility, + compressibilityAttributeName, + onPoints=compressibilityOnPoints, + logger=self.m_logger ): + self.m_logger.error( "Compressibility coefficient computation failed." ) + return False # oedometric compressibility - compressibilityOedAttributeName: str = ( PostProcessingOutputsEnum.COMPRESSIBILITY_OED.attributeName ) - if not isAttributeInObject( self.output, compressibilityOedAttributeName, self.m_attributeOnPoints ): - shearModulusAttributeName: str = ( GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName ) - shearModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, shearModulusAttributeName, - self.m_attributeOnPoints ) - try: - assert poissonRatio is not None, ( f"{poissonRatioAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert bulkModulus is not None, ( f"{bulkModulusAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert porosityInitial is not None, ( f"{porosityInitialAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) - compressibilityOed: npt.NDArray[ np.float64 ] = fcts.compressibilityOed( - bulkModulus, shearModulus, porosityInitial ) - createAttribute( - self.output, - compressibilityOed, - compressibilityOedAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Oedometric Compressibility was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False + compressibilityOedAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY_OED.attributeName + compressibilityOedOnPoints: bool = PostProcessingOutputsEnum.COMPRESSIBILITY_OED.isOnPoints + compressibilityOed: npt.NDArray[ np.float64 ] = fcts.compressibilityOed( self.shearModulus, self.bulkModulus, porosity ) + if not createAttribute( self.output, + compressibilityOed, + compressibilityOedAttributeName, + onPoints=compressibilityOedOnPoints, + logger=self.m_logger ): + self.m_logger.error( "Oedometric compressibility coefficient computation failed." ) + return False # real compressibility - compressibilityRealAttributeName: str = ( PostProcessingOutputsEnum.COMPRESSIBILITY_REAL.attributeName ) - if not isAttributeInObject( self.output, compressibilityRealAttributeName, self.m_attributeOnPoints ): - deltaPressureAttributeName: str = ( GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName ) - deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, - self.m_attributeOnPoints ) - try: - assert deltaPressure is not None, ( f"{deltaPressureAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert porosityInitial is not None, ( f"{porosityInitialAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert porosity is not None, ( f"{porosityAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) - compressibilityReal: npt.NDArray[ np.float64 ] = fcts.compressibilityReal( - deltaPressure, porosity, porosityInitial ) - createAttribute( - self.output, - compressibilityReal, - compressibilityRealAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Real compressibility was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False + porosityInitialAttributeName: str = GeosMeshOutputsEnum.POROSITY_INI.attributeName + porosityInitialOnPoints: bool = GeosMeshOutputsEnum.POROSITY_INI.isOnPoints + porosityInitial: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityInitialAttributeName, porosityInitialOnPoints ) + + deltaPressureAttributeName: str = GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName + deltaPressureOnPoint: str = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints + deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ) + + compressibilityRealAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY_REAL.attributeName + compressibilityRealOnPoint: bool = PostProcessingOutputsEnum.COMPRESSIBILITY_REAL.isOnPoints + compressibilityReal: npt.NDArray[ np.float64 ] = fcts.compressibilityReal( deltaPressure, porosity, porosityInitial ) + if not createAttribute( self.output, + compressibilityReal, + compressibilityRealAttributeName, + onPoints=compressibilityRealOnPoint, + logger=self.m_logger ): + self.m_logger.error( "Real compressibility coefficient computation failed.") + return False return True From 424d76d1dcf4b44007753cca480daccbe313afef Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Mon, 22 Sep 2025 15:37:01 +0200 Subject: [PATCH 08/40] refactor computeRealEffectiveStressRatio --- .../mesh/processing/GeomechanicsCalculator.py | 54 ++++++------------- 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index 6268b3eb5..c1ef8871a 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -535,20 +535,8 @@ def computeRealEffectiveStressRatio( self: Self ) -> bool: Returns: bool: True if calculation successfully ended, False otherwise. """ - # test if effective stress is in the table - if not isAttributeInObject( - self.output, - GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName, - self.m_attributeOnPoints, - ): - self.m_logger.error( "Effective stress is not in input data." ) - return False - - # real effective stress ratio - return self.computeStressRatioReal( - GeosMeshOutputsEnum.STRESS_EFFECTIVE, - PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_REAL, - ) + return self.computeStressRatioReal( GeosMeshOutputsEnum.STRESS_EFFECTIVE, + PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_REAL ) def computeTotalStresses( self: Self ) -> bool: """Compute total stress total stress ratio. @@ -949,32 +937,22 @@ def computeStressRatioReal( self: Self, inputAttribute: AttributeEnum, outputAtt Returns: bool: return True if calculation successfully ended, False otherwise. """ - stressRatioRealAttributeName = outputAttribute.attributeName - if not isAttributeInObject( self.output, stressRatioRealAttributeName, self.m_attributeOnPoints ): - try: - stressAttributeName: str = inputAttribute.attributeName - stress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, stressAttributeName, - self.m_attributeOnPoints ) - assert stress is not None, ( f"{stressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - - verticalStress: npt.NDArray[ np.float64 ] = stress[ :, 2 ] - # keep the minimum of the 2 horizontal components - horizontalStress: npt.NDArray[ np.float64 ] = np.min( stress[ :, :2 ], axis=1 ) + stressAttributeName: str = inputAttribute.attributeName + stressOnPoints: bool = inputAttribute.isOnPoints + stress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, stressAttributeName, stressOnPoints ) - stressRatioReal: npt.NDArray[ np.float64 ] = fcts.stressRatio( horizontalStress, verticalStress ) - createAttribute( - self.output, - stressRatioReal, - stressRatioRealAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( f"{outputAttribute.attributeName} was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False + verticalStress: npt.NDArray[ np.float64 ] = stress[ :, 2 ] + # keep the minimum of the 2 horizontal components + horizontalStress: npt.NDArray[ np.float64 ] = np.min( stress[ :, :2 ], axis=1 ) - return True + stressRatioRealAttributeName: str = outputAttribute.attributeName + stressRatioRealOnPoints: bool = outputAttribute.isOnPoints + stressRatioReal: npt.NDArray[ np.float64 ] = fcts.stressRatio( horizontalStress, verticalStress ) + return createAttribute( self.output, + stressRatioReal, + stressRatioRealAttributeName, + onPoints=stressRatioRealOnPoints, + logger=self.m_logger) def computeEffectiveStressRatioOed( self: Self ) -> bool: """Compute the effective stress ratio in oedometric conditions. From 6a55e7f3768992d3313d1f0921d0e882a1095c23 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Mon, 22 Sep 2025 15:42:45 +0200 Subject: [PATCH 09/40] refactor computeSpecificGravity --- .../mesh/processing/GeomechanicsCalculator.py | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index c1ef8871a..05813b0e5 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -507,27 +507,17 @@ def computeSpecificGravity( self: Self ) -> bool: bool: True if the attribute is correctly created, False otherwise. """ densityAttributeName: str = GeosMeshOutputsEnum.ROCK_DENSITY.attributeName - density: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, densityAttributeName, - self.m_attributeOnPoints ) - specificGravityAttributeName: str = ( PostProcessingOutputsEnum.SPECIFIC_GRAVITY.attributeName ) - if not isAttributeInObject( self.output, specificGravityAttributeName, self.m_attributeOnPoints ): - try: - assert density is not None, ( f"{densityAttributeName} " + UNDEFINED_ATTRIBUTE_MESSAGE ) + densityOnPoints: bool = GeosMeshOutputsEnum.ROCK_DENSITY.isOnPoints + density: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, densityAttributeName, densityOnPoints ) - specificGravity: npt.NDArray[ np.float64 ] = fcts.specificGravity( density, self.m_specificDensity ) - createAttribute( - self.output, - specificGravity, - specificGravityAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Specific gravity was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False - - return True + specificGravityAttributeName: str = PostProcessingOutputsEnum.SPECIFIC_GRAVITY.attributeName + specificGravityOnPoints: bool = PostProcessingOutputsEnum.SPECIFIC_GRAVITY.isOnPoints + specificGravity: npt.NDArray[ np.float64 ] = fcts.specificGravity( density, self.m_specificDensity ) + return createAttribute( self.output, + specificGravity, + specificGravityAttributeName, + onPoints=specificGravityOnPoints, + logger=self.m_logger ) def computeRealEffectiveStressRatio( self: Self ) -> bool: """Compute effective stress ratio. From 841826189b58eac6292352aee83112025642b3a1 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Mon, 22 Sep 2025 17:09:13 +0200 Subject: [PATCH 10/40] refactor computeTotalStresses --- .../mesh/processing/GeomechanicsCalculator.py | 223 +++++++----------- 1 file changed, 89 insertions(+), 134 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index 05813b0e5..ef812e497 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -286,7 +286,6 @@ def computeBasicOutputs( self: Self ) -> bool: # return False if not self.computeTotalStresses(): - self.m_logger.error( "Total stresses computation failed." ) return False if not self.computeElasticStrain(): @@ -481,7 +480,7 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: porosityInitial: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityInitialAttributeName, porosityInitialOnPoints ) deltaPressureAttributeName: str = GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName - deltaPressureOnPoint: str = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints + deltaPressureOnPoint: bool = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ) compressibilityRealAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY_REAL.attributeName @@ -532,52 +531,36 @@ def computeTotalStresses( self: Self ) -> bool: """Compute total stress total stress ratio. Total stress is computed at the initial and current time steps. - total stress ratio is computed at current time step only. + Total stress ratio is computed at current time step only. Returns: bool: True if calculation successfully ended, False otherwise. """ - # compute total stress at initial time step - totalStressT0AttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.attributeName ) - if not isAttributeInObject( self.output, totalStressT0AttributeName, self.m_attributeOnPoints ): - self.computeTotalStressInitial() - - # compute total stress at current time step - totalStressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL.attributeName ) - if not isAttributeInObject( self.output, totalStressAttributeName, self.m_attributeOnPoints ): - try: - effectiveStressAttributeName: str = ( GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName ) - effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( - self.output, - effectiveStressAttributeName, - self.m_attributeOnPoints, - ) - assert effectiveStress is not None, ( f"{effectiveStressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - - pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName - pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, - self.m_attributeOnPoints ) - assert pressure is not None, ( f"{pressureAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - - biotCoefficientAttributeName: str = ( PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName ) - biotCoefficient: npt.NDArray[ np.float64 ] = getArrayInObject( - self.output, - biotCoefficientAttributeName, - self.m_attributeOnPoints, - ) - assert biotCoefficient is not None, ( f"{biotCoefficientAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) + # Compute total stress at initial time step. + if not self.computeTotalStressInitial(): + self.m_logger.error( "Total stress at initial time step computation failed.") + return False - self.doComputeTotalStress( effectiveStress, pressure, biotCoefficient, totalStressAttributeName ) - self.computeStressRatioReal( - PostProcessingOutputsEnum.STRESS_TOTAL, - PostProcessingOutputsEnum.STRESS_TOTAL_RATIO_REAL, - ) + # Compute total stress at current time step. + effectiveStressAttributeName: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName + effectiveStressOnPoints: bool = GeosMeshOutputsEnum.STRESS_EFFECTIVE.isOnPoints + effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressAttributeName, effectiveStressOnPoints ) - except AssertionError as e: - self.m_logger.error( "Total stress at current time step was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False + pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName + pressureOnPoints: bool = GeosMeshOutputsEnum.PRESSURE.isOnPoints + pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, pressureOnPoints ) + totalStressAttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL.attributeName + totalStressOnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL.isOnPoints + if not self.doComputeTotalStress( effectiveStress, pressure, self.biotCoefficient, totalStressAttributeName, totalStressOnPoints ): + self.m_logger.error( "Total stress at current time step computation failed.") + return False + + # Compute total stress ratio. + if not self.computeStressRatioReal( PostProcessingOutputsEnum.STRESS_TOTAL, PostProcessingOutputsEnum.STRESS_TOTAL_RATIO_REAL ): + self.m_logger.error( "Total stress ratio computation failed.") + return False + return True def computeTotalStressInitial( self: Self ) -> bool: @@ -586,76 +569,56 @@ def computeTotalStressInitial( self: Self ) -> bool: Returns: bool: True if calculation successfully ended, False otherwise. """ - try: - - # compute elastic moduli at initial time step - bulkModulusT0: npt.NDArray[ np.float64 ] - - bulkModulusT0AttributeName: str = ( PostProcessingOutputsEnum.BULK_MODULUS_INITIAL.attributeName ) - youngModulusT0AttributeName: str = ( PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.attributeName ) - poissonRatioT0AttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.attributeName ) - # get bulk modulus at initial time step - if isAttributeInObject( self.output, bulkModulusT0AttributeName, self.m_attributeOnPoints ): + bulkModulusT0AttributeName: str = PostProcessingOutputsEnum.BULK_MODULUS_INITIAL.attributeName + youngModulusT0AttributeName: str = PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.attributeName + poissonRatioT0AttributeName: str = PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.attributeName - bulkModulusT0 = getArrayInObject( self.output, bulkModulusT0AttributeName, self.m_attributeOnPoints ) - # or compute it from Young and Poisson if not an attribute - elif isAttributeInObject( self.output, youngModulusT0AttributeName, - self.m_attributeOnPoints ) and isAttributeInObject( - self.output, poissonRatioT0AttributeName, self.m_attributeOnPoints ): - - youngModulusT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, - youngModulusT0AttributeName, - self.m_attributeOnPoints ) - poissonRatioT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, - poissonRatioT0AttributeName, - self.m_attributeOnPoints ) - assert poissonRatioT0 is not None, "Poisson's ratio is undefined." - assert youngModulusT0 is not None, "Young modulus is undefined." - bulkModulusT0 = fcts.bulkModulus( youngModulusT0, poissonRatioT0 ) - else: - raise AssertionError( "Elastic moduli at initial time are absent." ) - - assert ( bulkModulusT0 is not None ), "Bulk modulus at initial time step is undefined." - - # compute Biot at initial time step - biotCoefficientT0: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.m_grainBulkModulus, - bulkModulusT0 ) - assert ( biotCoefficientT0 is not None ), "Biot coefficient at initial time step is undefined." - - # compute total stress at initial time step - # get effective stress attribute - effectiveStressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.attributeName ) - effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressAttributeName, - self.m_attributeOnPoints ) - assert effectiveStress is not None, ( f"{effectiveStressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - - # get pressure attribute - pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName - pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, - self.m_attributeOnPoints ) - pressureT0: npt.NDArray[ np.float64 ] - # case pressureT0 is None, total stress = effective stress - # (managed by doComputeTotalStress function) - if pressure is not None: - # get delta pressure to recompute pressure at initial time step (pressureTo) - deltaPressureAttributeName: str = ( GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName ) - deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, - self.m_attributeOnPoints ) - assert deltaPressure is not None, ( f"{deltaPressureAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - pressureT0 = pressure - deltaPressure - - totalStressT0AttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.attributeName ) - return self.doComputeTotalStress( - effectiveStress, - pressureT0, - biotCoefficientT0, - totalStressT0AttributeName, - ) - - except AssertionError as e: - self.m_logger.error( "Total stress at initial time step was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False + bulkModulusT0OnPoints: bool = PostProcessingOutputsEnum.BULK_MODULUS_INITIAL.isOnPoints + youngModulusT0OnPoints: bool = PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.isOnPoints + poissonRatioT0OnPoints: bool = PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.isOnPoints + + # Compute BulkModulus at initial time step. + bulkModulusT0: npt.NDArray[ np.float64 ] + if isAttributeInObject( self.output, bulkModulusT0AttributeName, bulkModulusT0OnPoints ): + bulkModulusT0 = getArrayInObject( self.output, bulkModulusT0AttributeName, bulkModulusT0OnPoints ) + elif isAttributeInObject( self.output, youngModulusT0AttributeName, youngModulusT0OnPoints ) \ + and isAttributeInObject( self.output, poissonRatioT0AttributeName, poissonRatioT0OnPoints ): + youngModulusT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, youngModulusT0AttributeName, youngModulusT0OnPoints ) + poissonRatioT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioT0AttributeName, poissonRatioT0OnPoints ) + bulkModulusT0 = fcts.bulkModulus( youngModulusT0, poissonRatioT0 ) + else: + self.m_logger( "Elastic moduli at initial time are absent." ) + + # Compute Biot at initial time step. + biotCoefficientT0: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.m_grainBulkModulus, bulkModulusT0 ) + + # Compute total stress at initial time step. + effectiveStressT0AttributeName: str = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.attributeName + effectiveStressT0OnPoints: bool = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.isOnPoints + effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressT0AttributeName, effectiveStressT0OnPoints ) + + # Get pressure attribute. + pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName + pressureOnPoints: bool = GeosMeshOutputsEnum.PRESSURE.isOnPoints + pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, pressureOnPoints ) + + pressureT0: npt.NDArray[ np.float64 ] + # Case pressureT0 is None, total stress = effective stress + # (managed by doComputeTotalStress function) + if pressure is not None: + # Get delta pressure to recompute pressure at initial time step (pressureTo) + deltaPressureAttributeName: str = GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName + deltaPressureOnPoint: bool = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints + deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ) + pressureT0 = pressure - deltaPressure + + totalStressT0AttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.attributeName + totalStressT0OnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.isOnPoints + return self.doComputeTotalStress( effectiveStress, + pressureT0, + biotCoefficientT0, + totalStressT0AttributeName, + totalStressT0OnPoints ) def doComputeTotalStress( self: Self, @@ -663,45 +626,37 @@ def doComputeTotalStress( pressure: Union[ npt.NDArray[ np.float64 ], None ], biotCoefficient: npt.NDArray[ np.float64 ], totalStressAttributeName: str, + totalStressOnPoints: bool, ) -> bool: """Compute total stress from effective stress and pressure. Args: - effectiveStress (npt.NDArray[np.float64]): effective stress - pressure (npt.NDArray[np.float64] | None): pressure - biotCoefficient (npt.NDArray[np.float64]): biot coefficient - totalStressAttributeName (str): total stress attribute name + effectiveStress (npt.NDArray[np.float64]): Effective stress. + pressure (npt.NDArray[np.float64] | None): Pressure. + biotCoefficient (npt.NDArray[np.float64]): Biot coefficient. + totalStressAttributeName (str): Total stress attribute name. + totalStressOnPoints (bool): True if on points, False if on cells. Returns: - bool: return True if calculation successfully ended, False otherwise. + bool: True if calculation successfully ended, False otherwise. """ totalStress: npt.NDArray[ np.float64 ] - # if pressure data is missing, total stress equals effective stress if pressure is None: - # copy effective stress data totalStress = np.copy( effectiveStress ) - mess: str = ( "Pressure attribute is undefined, " + "total stress will be equal to effective stress." ) - self.m_logger.warning( mess ) + self.m_logger.warning( "Pressure attribute is undefined, total stress will be equal to effective stress." ) else: if np.isnan( pressure ).any(): - mess0: str = ( "Some cells do not have pressure data, " + - "total stress will be equal to effective stress." ) - self.m_logger.warning( mess0 ) + self.m_logger.warning( "Some cells do not have pressure data, for those cells, pressure is set to 0." ) + pressure[ np.isnan( pressure ) ] = 0.0 - # replace nan values by 0. - pressure[ np.isnan( pressure ) ] = 0.0 totalStress = fcts.totalStress( effectiveStress, biotCoefficient, pressure ) - # create total stress attribute - assert totalStress is not None, "Total stress data is null." - createAttribute( - self.output, - totalStress, - totalStressAttributeName, - ComponentNameEnum.XYZ.value, - self.m_attributeOnPoints, - ) - return True + return createAttribute( self.output, + totalStress, + totalStressAttributeName, + attributeName=ComponentNameEnum.XYZ.value, + onPoints=totalStressOnPoints, + logger=self.m_logger ) def computeLithostaticStress( self: Self ) -> bool: """Compute lithostatic stress. From fadec20cbfcaae5ce660bbb3514e817c2741dfb2 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Mon, 22 Sep 2025 19:05:44 +0200 Subject: [PATCH 11/40] refactor computBasicOutputs --- .../mesh/processing/GeomechanicsCalculator.py | 431 +++++++----------- 1 file changed, 174 insertions(+), 257 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index ef812e497..09bc1772a 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -145,14 +145,7 @@ def applyFilter( self: Self ) -> bool: Boolean (bool): True if calculation successfully ended, False otherwise. """ if not self.checkMandatoryAttributes(): - mess: str = ( "Mandatory properties are missing to compute geomechanical outputs:" ) - mess += ( f"Either {PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName} " - f"and {PostProcessingOutputsEnum.POISSON_RATIO.attributeName} or " - f"{GeosMeshOutputsEnum.BULK_MODULUS.attributeName} and " - f"{GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName} must be " - f"present in the data as well as the " - f"{GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName} attribute." ) - self.m_logger.error( mess ) + self.m_logger.error( "The filter failed." ) return False if not self.computeBasicOutputs(): @@ -246,14 +239,76 @@ def checkMandatoryAttributes( self: Self ) -> bool: if not isAttributeInObject( self.output, self.bulkModulusAttributeName, self.bulkModulusOnPoints ) \ or not isAttributeInObject( self.output, self.shearModulusAttributeName, self.shearModulusOnPoints ): + mess: str = ( "Mandatory properties are missing to compute geomechanical outputs:" ) + mess += ( f"Either { self.bulkModulusAttributeName } and { self.shearModulusAttributeName } or { self.youngModulusAttributeName } and { self.poissonRatioAttributeName } must be present in the table data." ) + self.m_logger.error( mess ) return False + + porosityAttributeName: str = GeosMeshOutputsEnum.POROSITY.attributeName + porosityOnPoints: bool = GeosMeshOutputsEnum.POROSITY.isOnPoints + if not isAttributeInObject( self.output, porosityAttributeName, porosityOnPoints ): + self.m_logger.error( f"The mandatory attribute { porosityAttributeName } is missing to compute geomechanical outputs." ) + return False + else: + self.porosity: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityAttributeName, porosityOnPoints ) + + porosityInitialAttributeName: str = GeosMeshOutputsEnum.POROSITY_INI.attributeName + porosityInitialOnPoints: bool = GeosMeshOutputsEnum.POROSITY_INI.isOnPoints + if not isAttributeInObject( self.output, porosityInitialAttributeName, porosityInitialOnPoints ): + self.m_logger.error( f"The mandatory attribute { porosityInitialAttributeName } is missing to compute geomechanical outputs." ) + return False + else: + self.porosityInitial: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityInitialAttributeName, porosityInitialOnPoints ) + + deltaPressureAttributeName: str = GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName + deltaPressureOnPoint: bool = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints + if not isAttributeInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ): + self.m_logger.error( f"The mandatory attribute { deltaPressureAttributeName } is missing to compute geomechanical outputs." ) + return False + else: + self.deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ) + + densityAttributeName: str = GeosMeshOutputsEnum.ROCK_DENSITY.attributeName + densityOnPoints: bool = GeosMeshOutputsEnum.ROCK_DENSITY.isOnPoints + if not isAttributeInObject( self.output, densityAttributeName, densityOnPoints ): + self.m_logger.error( f"The mandatory attribute { densityAttributeName } is missing to compute geomechanical outputs." ) + return False + else: + self.density: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, densityAttributeName, densityOnPoints ) - self.effectiveStressAttributeName: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName - self.effectiveStressOnPoints: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.isOnPoints + effectiveStressAttributeName: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName + effectiveStressOnPoints: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.isOnPoints + if not isAttributeInObject( self.output, effectiveStressAttributeName, effectiveStressOnPoints ): + self.m_logger.error( f"The mandatory attribute { effectiveStressAttributeName } is missing to compute geomechanical outputs." ) + return False + else: + self.effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressAttributeName, effectiveStressOnPoints ) + + effectiveStressT0AttributeName: str = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.attributeName + effectiveStressT0OnPoints: bool = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.isOnPoints + if not isAttributeInObject( self.output, effectiveStressT0AttributeName, effectiveStressT0OnPoints ): + self.m_logger.error( f"The mandatory attribute { effectiveStressT0AttributeName } is missing to compute geomechanical outputs." ) + return False + else: + self.effectiveStressT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressT0AttributeName, effectiveStressT0OnPoints ) + + pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName + pressureOnPoints: bool = GeosMeshOutputsEnum.PRESSURE.isOnPoints + if not isAttributeInObject( self.output, pressureAttributeName, pressureOnPoints ): + self.m_logger.error( f"The mandatory attribute { pressureAttributeName } is missing to compute geomechanical outputs." ) + return False + else: + self.pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, pressureOnPoints ) + + deltaPressureAttributeName: str = GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName + deltaPressureOnPoint: bool = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints + if not isAttributeInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ): + self.m_logger.error( f"The mandatory attribute { deltaPressureAttributeName } is missing to compute geomechanical outputs." ) + return False + else: + self.deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ) - # check effective Stress is present - isAllGood: bool = isAttributeInObject( self.output, self.effectiveStressAttributeName, self.effectiveStressOnPoints ) - return isAllGood + return True def computeBasicOutputs( self: Self ) -> bool: """Compute basic geomechanical outputs. @@ -302,7 +357,6 @@ def computeBasicOutputs( self: Self ) -> bool: return False if not self.computeReservoirStressPathReal(): - self.m_logger.error( "Reservoir stress path computation failed." ) return False self.m_logger.info( "All geomechanical basic outputs were successfully computed." ) @@ -429,7 +483,6 @@ def computeBiotCoefficient( self: Self ) -> bool: """ biotCoefficientAttributeName: str = PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName biotCoefficientOnPoints: bool = PostProcessingOutputsEnum.BIOT_COEFFICIENT.isOnPoints - self.biotCoefficient: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.m_grainBulkModulus, self.bulkModulus ) return createAttribute( self.output, self.biotCoefficient, @@ -446,14 +499,10 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: Returns: bool: True if the attribute is correctly created, False otherwise. """ - porosityAttributeName: str = GeosMeshOutputsEnum.POROSITY.attributeName - porosityOnPoints: bool = GeosMeshOutputsEnum.POROSITY.isOnPoints - porosity: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityAttributeName, porosityOnPoints ) - compressibilityAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY.attributeName compressibilityOnPoints: bool = PostProcessingOutputsEnum.COMPRESSIBILITY.isOnPoints compressibility: npt.NDArray[ np.float64 ] = fcts.compressibility( self.poissonRatio, self.bulkModulus, - self.biotCoefficient, porosity ) + self.biotCoefficient, self.porosity ) if not createAttribute( self.output, compressibility, compressibilityAttributeName, @@ -465,7 +514,7 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: # oedometric compressibility compressibilityOedAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY_OED.attributeName compressibilityOedOnPoints: bool = PostProcessingOutputsEnum.COMPRESSIBILITY_OED.isOnPoints - compressibilityOed: npt.NDArray[ np.float64 ] = fcts.compressibilityOed( self.shearModulus, self.bulkModulus, porosity ) + compressibilityOed: npt.NDArray[ np.float64 ] = fcts.compressibilityOed( self.shearModulus, self.bulkModulus, self.porosity ) if not createAttribute( self.output, compressibilityOed, compressibilityOedAttributeName, @@ -475,17 +524,9 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: return False # real compressibility - porosityInitialAttributeName: str = GeosMeshOutputsEnum.POROSITY_INI.attributeName - porosityInitialOnPoints: bool = GeosMeshOutputsEnum.POROSITY_INI.isOnPoints - porosityInitial: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityInitialAttributeName, porosityInitialOnPoints ) - - deltaPressureAttributeName: str = GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName - deltaPressureOnPoint: bool = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints - deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ) - compressibilityRealAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY_REAL.attributeName compressibilityRealOnPoint: bool = PostProcessingOutputsEnum.COMPRESSIBILITY_REAL.isOnPoints - compressibilityReal: npt.NDArray[ np.float64 ] = fcts.compressibilityReal( deltaPressure, porosity, porosityInitial ) + compressibilityReal: npt.NDArray[ np.float64 ] = fcts.compressibilityReal( self.deltaPressure, self.porosity, self.porosityInitial ) if not createAttribute( self.output, compressibilityReal, compressibilityRealAttributeName, @@ -505,13 +546,9 @@ def computeSpecificGravity( self: Self ) -> bool: Returns: bool: True if the attribute is correctly created, False otherwise. """ - densityAttributeName: str = GeosMeshOutputsEnum.ROCK_DENSITY.attributeName - densityOnPoints: bool = GeosMeshOutputsEnum.ROCK_DENSITY.isOnPoints - density: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, densityAttributeName, densityOnPoints ) - specificGravityAttributeName: str = PostProcessingOutputsEnum.SPECIFIC_GRAVITY.attributeName specificGravityOnPoints: bool = PostProcessingOutputsEnum.SPECIFIC_GRAVITY.isOnPoints - specificGravity: npt.NDArray[ np.float64 ] = fcts.specificGravity( density, self.m_specificDensity ) + specificGravity: npt.NDArray[ np.float64 ] = fcts.specificGravity( self.density, self.m_specificDensity ) return createAttribute( self.output, specificGravity, specificGravityAttributeName, @@ -524,8 +561,7 @@ def computeRealEffectiveStressRatio( self: Self ) -> bool: Returns: bool: True if calculation successfully ended, False otherwise. """ - return self.computeStressRatioReal( GeosMeshOutputsEnum.STRESS_EFFECTIVE, - PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_REAL ) + return self.computeStressRatioReal( self.effectiveStress, PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_REAL ) def computeTotalStresses( self: Self ) -> bool: """Compute total stress total stress ratio. @@ -542,22 +578,23 @@ def computeTotalStresses( self: Self ) -> bool: return False # Compute total stress at current time step. - effectiveStressAttributeName: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName - effectiveStressOnPoints: bool = GeosMeshOutputsEnum.STRESS_EFFECTIVE.isOnPoints - effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressAttributeName, effectiveStressOnPoints ) - - pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName - pressureOnPoints: bool = GeosMeshOutputsEnum.PRESSURE.isOnPoints - pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, pressureOnPoints ) - totalStressAttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL.attributeName totalStressOnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL.isOnPoints - if not self.doComputeTotalStress( effectiveStress, pressure, self.biotCoefficient, totalStressAttributeName, totalStressOnPoints ): + totalStressComponentNames: tuple[ str, ...] = tuple() + if PostProcessingOutputsEnum.STRESS_TOTAL.nbComponent > 1: + totalStressComponentNames = getComponentNames( self.output, totalStressAttributeName, totalStressOnPoints ) + self.totalStress: npt.NDArray[ np.float64 ] = self.doComputeTotalStress( self.effectiveStress, self.pressure, self.biotCoefficient ) + if not createAttribute( self.output, + self.totalStress, + totalStressAttributeName, + componentNames=totalStressComponentNames, + onPoints=totalStressOnPoints, + logger=self.m_logger ): self.m_logger.error( "Total stress at current time step computation failed.") return False # Compute total stress ratio. - if not self.computeStressRatioReal( PostProcessingOutputsEnum.STRESS_TOTAL, PostProcessingOutputsEnum.STRESS_TOTAL_RATIO_REAL ): + if not self.computeStressRatioReal( self.totalStress, PostProcessingOutputsEnum.STRESS_TOTAL_RATIO_REAL ): self.m_logger.error( "Total stress ratio computation failed.") return False @@ -592,53 +629,41 @@ def computeTotalStressInitial( self: Self ) -> bool: # Compute Biot at initial time step. biotCoefficientT0: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.m_grainBulkModulus, bulkModulusT0 ) - # Compute total stress at initial time step. - effectiveStressT0AttributeName: str = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.attributeName - effectiveStressT0OnPoints: bool = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.isOnPoints - effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressT0AttributeName, effectiveStressT0OnPoints ) - - # Get pressure attribute. - pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName - pressureOnPoints: bool = GeosMeshOutputsEnum.PRESSURE.isOnPoints - pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, pressureOnPoints ) - pressureT0: npt.NDArray[ np.float64 ] # Case pressureT0 is None, total stress = effective stress # (managed by doComputeTotalStress function) - if pressure is not None: + if self.pressure is not None: # Get delta pressure to recompute pressure at initial time step (pressureTo) - deltaPressureAttributeName: str = GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName - deltaPressureOnPoint: bool = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints - deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ) - pressureT0 = pressure - deltaPressure + pressureT0 = self.pressure - self.deltaPressure totalStressT0AttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.attributeName totalStressT0OnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.isOnPoints - return self.doComputeTotalStress( effectiveStress, - pressureT0, - biotCoefficientT0, - totalStressT0AttributeName, - totalStressT0OnPoints ) + totalStressT0ComponentNames: tuple[ str, ...] = tuple() + if PostProcessingOutputsEnum.STRESS_TOTAL.nbComponent > 1: + totalStressT0ComponentNames = getComponentNames( self.output, totalStressT0AttributeName, totalStressT0OnPoints ) + self.totalStressT0 = self.doComputeTotalStress( self.effectiveStressT0, pressureT0, biotCoefficientT0 ) + return createAttribute( self.output, + self.totalStressT0, + totalStressT0AttributeName, + componentNames=totalStressT0ComponentNames, + onPoints=totalStressT0OnPoints, + logger=self.m_logger ) def doComputeTotalStress( self: Self, effectiveStress: npt.NDArray[ np.float64 ], pressure: Union[ npt.NDArray[ np.float64 ], None ], biotCoefficient: npt.NDArray[ np.float64 ], - totalStressAttributeName: str, - totalStressOnPoints: bool, - ) -> bool: + ) -> npt.NDArray[ np.float64 ]: """Compute total stress from effective stress and pressure. Args: effectiveStress (npt.NDArray[np.float64]): Effective stress. pressure (npt.NDArray[np.float64] | None): Pressure. biotCoefficient (npt.NDArray[np.float64]): Biot coefficient. - totalStressAttributeName (str): Total stress attribute name. - totalStressOnPoints (bool): True if on points, False if on cells. Returns: - bool: True if calculation successfully ended, False otherwise. + npt.NDArray[ np.float64 ]: TotalStress. """ totalStress: npt.NDArray[ np.float64 ] if pressure is None: @@ -651,12 +676,7 @@ def doComputeTotalStress( totalStress = fcts.totalStress( effectiveStress, biotCoefficient, pressure ) - return createAttribute( self.output, - totalStress, - totalStressAttributeName, - attributeName=ComponentNameEnum.XYZ.value, - onPoints=totalStressOnPoints, - logger=self.m_logger ) + return totalStress def computeLithostaticStress( self: Self ) -> bool: """Compute lithostatic stress. @@ -664,32 +684,16 @@ def computeLithostaticStress( self: Self ) -> bool: Returns: bool: True if calculation successfully ended, False otherwise. """ - lithostaticStressAttributeName: str = ( PostProcessingOutputsEnum.LITHOSTATIC_STRESS.attributeName ) - if not isAttributeInObject( self.output, lithostaticStressAttributeName, self.m_attributeOnPoints ): - - densityAttributeName: str = GeosMeshOutputsEnum.ROCK_DENSITY.attributeName - density: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, densityAttributeName, - self.m_attributeOnPoints ) - try: - depth: npt.NDArray[ np.float64 ] = self.computeDepthAlongLine( - ) if self.m_attributeOnPoints else self.computeDepthInMesh() - assert depth is not None, "Depth is undefined." - assert density is not None, ( f"{densityAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - - lithostaticStress = fcts.lithostaticStress( depth, density, GRAVITY ) - createAttribute( - self.output, - lithostaticStress, - lithostaticStressAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Lithostatic stress was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False + lithostaticStressAttributeName: str = PostProcessingOutputsEnum.LITHOSTATIC_STRESS.attributeName + lithostaticStressOnPoint: bool = PostProcessingOutputsEnum.LITHOSTATIC_STRESS.isOnPoints - return True + depth: npt.NDArray[ np.float64 ] = self.computeDepthAlongLine() if lithostaticStressOnPoint else self.computeDepthInMesh() + lithostaticStress = fcts.lithostaticStress( depth, self.density, GRAVITY ) + return createAttribute( self.output, + lithostaticStress, + lithostaticStressAttributeName, + onPoints=lithostaticStressOnPoint, + logger=self.m_logger) def computeDepthAlongLine( self: Self ) -> npt.NDArray[ np.float64 ]: """Compute depth along a line. @@ -740,48 +744,25 @@ def computeElasticStrain( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - elasticStrainAttributeName: str = ( PostProcessingOutputsEnum.STRAIN_ELASTIC.attributeName ) - if not isAttributeInObject( self.output, elasticStrainAttributeName, self.m_attributeOnPoints ): - effectiveStressAttributeName: str = ( GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName ) - effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressAttributeName, - self.m_attributeOnPoints ) - - effectiveStressIniAttributeName: str = ( PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.attributeName ) - effectiveStressIni: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, - effectiveStressIniAttributeName, - self.m_attributeOnPoints ) + deltaEffectiveStress = self.effectiveStress - self.effectiveStressT0 - bulkModulusAttributeName: str = ( GeosMeshOutputsEnum.BULK_MODULUS.attributeName ) - bulkModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, bulkModulusAttributeName, - self.m_attributeOnPoints ) - - shearModulusAttributeName: str = ( GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName ) - shearModulus: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, shearModulusAttributeName, - self.m_attributeOnPoints ) - - try: - assert effectiveStress is not None, ( f"{effectiveStressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert effectiveStressIni is not None, ( f"{effectiveStressIniAttributeName}" + - UNDEFINED_ATTRIBUTE_MESSAGE ) - assert bulkModulus is not None, ( f"{bulkModulusAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert shearModulus is not None, ( f"{shearModulusAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - - deltaEffectiveStress = effectiveStress - effectiveStressIni - elasticStrain: npt.NDArray[ np.float64 ] = ( fcts.elasticStrainFromBulkShear( - deltaEffectiveStress, bulkModulus, shearModulus ) ) - createAttribute( - self.output, - elasticStrain, - elasticStrainAttributeName, - ComponentNameEnum.XYZ.value, - self.m_attributeOnPoints, - ) + elasticStrainAttributeName: str = PostProcessingOutputsEnum.STRAIN_ELASTIC.attributeName + elasticStrainOnPoints: bool = PostProcessingOutputsEnum.STRAIN_ELASTIC.isOnPoints + elasticStrainComponentNames: tuple[ str, ...] = tuple() + if PostProcessingOutputsEnum.STRESS_TOTAL.nbComponent > 1: + elasticStrainComponentNames = getComponentNames( self.output, elasticStrainAttributeName, elasticStrainOnPoints ) + + if self.m_computeYoungPoisson: + elasticStrain: npt.NDArray[ np.float64 ] = fcts.elasticStrainFromBulkShear( deltaEffectiveStress, self.bulkModulus, self.shearModulus ) + else: + elasticStrain: npt.NDArray[ np.float64 ] = fcts.elasticStrainFromYoungPoisson( deltaEffectiveStress, self.youngModulus, self.poissonRatio ) - except AssertionError as e: - self.m_logger.error( "Elastic strain was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False - return True + return createAttribute( self.output, + elasticStrain, + elasticStrainAttributeName, + componentNames=elasticStrainComponentNames, + onPoints=elasticStrainOnPoints, + logger=self.m_logger ) def computeReservoirStressPathReal( self: Self ) -> bool: """Compute reservoir stress paths. @@ -789,56 +770,36 @@ def computeReservoirStressPathReal( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - RSPrealAttributeName: str = PostProcessingOutputsEnum.RSP_REAL.attributeName - if not isAttributeInObject( self.output, RSPrealAttributeName, self.m_attributeOnPoints ): - # real RSP - try: - # total stress at current and initial time steps - totalStressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL.attributeName ) - totalStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, totalStressAttributeName, - self.m_attributeOnPoints ) - - totalStressT0AttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.attributeName ) - totalStressIni: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, totalStressT0AttributeName, - self.m_attributeOnPoints ) - - deltaPressureAttributeName: str = ( GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName ) - deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, - self.m_attributeOnPoints ) - - assert totalStress is not None, ( f"{totalStressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert totalStressIni is not None, ( f"{totalStressT0AttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert deltaPressure is not None, ( f"{deltaPressureAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - - # create delta stress attribute for QC - deltaTotalStressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.attributeName ) - deltaStress: npt.NDArray[ np.float64 ] = totalStress - totalStressIni - componentNames: tuple[ str, ...] = getComponentNames( self.output, totalStressAttributeName, - self.m_attributeOnPoints ) - assert deltaStress is not None, ( f"{deltaTotalStressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - createAttribute( - self.output, - deltaStress, - deltaTotalStressAttributeName, - componentNames, - self.m_attributeOnPoints, - ) - - RSPreal: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathReal( deltaStress, deltaPressure ) - assert RSPreal is not None, ( f"{RSPrealAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - createAttribute( - self.output, - RSPreal, - RSPrealAttributeName, - componentNames, - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Reservoir stress path in real conditions was " + "not computed due to:" ) - self.m_logger.error( str( e ) ) - return False + # create delta stress attribute for QC + deltaTotalStressAttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.attributeName + deltaTotalStressOnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.isOnPoints + deltaTotalStressComponentNames: tuple[ str, ...] = tuple() + if PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.nbComponent > 1 : + deltaTotalStressComponentNames = getComponentNames( self.output, deltaTotalStressAttributeName, deltaTotalStressOnPoints ) + self.deltaTotalStress: npt.NDArray[ np.float64 ] = self.totalStress - self.totalStressT0 + if not createAttribute( self.output, + self.deltaTotalStress, + deltaTotalStressAttributeName, + componentNames=deltaTotalStressComponentNames, + onPoints=deltaTotalStressOnPoints, + logger=self.m_logger ): + self.m_logger.error( "Delta total stress computation failed.") + return False - return True + rspRealAttributeName: str = PostProcessingOutputsEnum.RSP_REAL.attributeName + rspRealOnPoints: bool = PostProcessingOutputsEnum.RSP_REAL.isOnPoints + rspRealComponentNames: tuple[ str, ...] = tuple() + if PostProcessingOutputsEnum.RSP_REAL.nbComponent > 1 : + rspRealComponentNames = getComponentNames( self.output, rspRealAttributeName, rspRealOnPoints ) + self.rspReal: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathReal( self.deltaTotalStress, self.deltaPressure ) + if not createAttribute( self.output, + self.rspReal, + rspRealAttributeName, + componentNames=rspRealComponentNames, + onPoints=rspRealOnPoints, + logger=self.m_logger ): + self.m_logger.error( "Reservoir stress real path computation failed." ) + return False def computeReservoirStressPathOed( self: Self ) -> bool: """Compute Reservoir Stress Path in oedometric conditions. @@ -846,55 +807,30 @@ def computeReservoirStressPathOed( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - RSPOedAttributeName: str = PostProcessingOutputsEnum.RSP_OED.attributeName - if not isAttributeInObject( self.output, RSPOedAttributeName, self.m_attributeOnPoints ): - - poissonRatioAttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO.attributeName ) - poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioAttributeName, - self.m_attributeOnPoints ) - - biotCoefficientAttributeName: str = ( PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName ) - biotCoefficient: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, biotCoefficientAttributeName, - self.m_attributeOnPoints ) - - try: - assert poissonRatio is not None, ( f"{poissonRatioAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert biotCoefficient is not None, ( f"{biotCoefficientAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - - RSPoed: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathOed( biotCoefficient, poissonRatio ) - createAttribute( - self.output, - RSPoed, - RSPOedAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Oedometric RSP was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False - - return True + rspOedAttributeName: str = PostProcessingOutputsEnum.RSP_OED.attributeName + rspOedOnPoints: bool = PostProcessingOutputsEnum.RSP_OED.isOnPoints + self.rspOed: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathOed( self.biotCoefficient, self.poissonRatio ) + return createAttribute( self.output, + self.rspOed, + rspOedAttributeName, + onPoints=rspOedOnPoints, + logger=self.m_logger ) - def computeStressRatioReal( self: Self, inputAttribute: AttributeEnum, outputAttribute: AttributeEnum ) -> bool: + def computeStressRatioReal( self: Self, stress: npt.NDArray[ np.float64 ], outputAttribute: AttributeEnum ) -> bool: """Compute the ratio between horizontal and vertical effective stress. Returns: bool: return True if calculation successfully ended, False otherwise. """ - stressAttributeName: str = inputAttribute.attributeName - stressOnPoints: bool = inputAttribute.isOnPoints - stress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, stressAttributeName, stressOnPoints ) - verticalStress: npt.NDArray[ np.float64 ] = stress[ :, 2 ] # keep the minimum of the 2 horizontal components horizontalStress: npt.NDArray[ np.float64 ] = np.min( stress[ :, :2 ], axis=1 ) stressRatioRealAttributeName: str = outputAttribute.attributeName stressRatioRealOnPoints: bool = outputAttribute.isOnPoints - stressRatioReal: npt.NDArray[ np.float64 ] = fcts.stressRatio( horizontalStress, verticalStress ) + self.stressRatioReal: npt.NDArray[ np.float64 ] = fcts.stressRatio( horizontalStress, verticalStress ) return createAttribute( self.output, - stressRatioReal, + self.stressRatioReal, stressRatioRealAttributeName, onPoints=stressRatioRealOnPoints, logger=self.m_logger) @@ -903,35 +839,16 @@ def computeEffectiveStressRatioOed( self: Self ) -> bool: """Compute the effective stress ratio in oedometric conditions. Returns: - bool: return True if calculation successfully ended, False otherwise. + bool: True if calculation successfully ended, False otherwise. """ - effectiveStressRatioOedAttributeName: str = ( - PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_OED.attributeName ) - if not isAttributeInObject( - self.output, - effectiveStressRatioOedAttributeName, - self.m_attributeOnPoints, - ): - poissonRatioAttributeName: str = ( PostProcessingOutputsEnum.POISSON_RATIO.attributeName ) - poissonRatio: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioAttributeName, - self.m_attributeOnPoints ) - - try: - assert poissonRatio is not None, ( f"{poissonRatioAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - - effectiveStressRatioOed: npt.NDArray[ np.float64 ] = ( fcts.deviatoricStressPathOed( poissonRatio ) ) - createAttribute( - self.output, - effectiveStressRatioOed, - effectiveStressRatioOedAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Oedometric effective stress ratio was not computed due to:" ) - self.m_logger.error( str( e ) ) - return False - return True + effectiveStressRatioOedAttributeName: str = PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_OED.attributeName + effectiveStressRatioOedOnPoints: bool = PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_OED.isOnPoints + effectiveStressRatioOed: npt.NDArray[ np.float64 ] = fcts.deviatoricStressPathOed( self.poissonRatio ) + return createAttribute( self.output, + effectiveStressRatioOed, + effectiveStressRatioOedAttributeName, + onPoints=effectiveStressRatioOedOnPoints, + logger=self.m_logger ) def computeCriticalTotalStressRatio( self: Self ) -> bool: """Compute fracture index and fracture threshold. From d267ad797e1ffd808981d5102f109a91fc62388b Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Mon, 22 Sep 2025 19:21:00 +0200 Subject: [PATCH 12/40] refactor computeAdvence --- .../mesh/processing/GeomechanicsCalculator.py | 183 ++++++------------ 1 file changed, 55 insertions(+), 128 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index 09bc1772a..a582113f1 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -368,21 +368,16 @@ def computeAdvancedOutputs( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - fractureIndexesComputed: bool = False - criticalPorePressure: bool = False - if self.m_totalStressComputed: - fractureIndexesComputed = self.computeCriticalTotalStressRatio() - criticalPorePressure = self.computeCriticalPorePressure() - - if ( self.m_effectiveStressRatioOedComputed and fractureIndexesComputed and criticalPorePressure ): - mess: str = ( "All geomechanical advanced outputs were " + "successfully computed." ) - self.m_logger.info( mess ) - return True - else: - mess0: str = ( "Some geomechanical advanced outputs were " + "not computed." ) - self.m_logger.error( mess0 ) + if not self.computeCriticalTotalStressRatio(): + return False + + if not self.computeCriticalPorePressure(): return False + mess: str = ( "All geomechanical advanced outputs were successfully computed." ) + self.m_logger.info( mess ) + return True + def computeElasticModulus( self: Self ) -> bool: """Compute elastic moduli. @@ -856,68 +851,34 @@ def computeCriticalTotalStressRatio( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - ret: bool = True - - fractureIndexAttributeName = ( PostProcessingOutputsEnum.CRITICAL_TOTAL_STRESS_RATIO.attributeName ) - if not isAttributeInObject( self.output, fractureIndexAttributeName, self.m_attributeOnPoints ): - - stressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL.attributeName ) - stress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, stressAttributeName, - self.m_attributeOnPoints ) + fractureIndexAttributeName: str = PostProcessingOutputsEnum.CRITICAL_TOTAL_STRESS_RATIO.attributeName + fractureIndexOnPoints: bool = PostProcessingOutputsEnum.CRITICAL_TOTAL_STRESS_RATIO.isOnPoints + verticalStress: npt.NDArray[ np.float64 ] = self.totalStress[ :, 2 ] + criticalTotalStressRatio: npt.NDArray[ np.float64 ] = fcts.criticalTotalStressRatio( self.pressure, verticalStress ) + if not createAttribute( self.output, + criticalTotalStressRatio, + fractureIndexAttributeName, + onPoints=fractureIndexOnPoints, + logger=self.m_logger ): + self.m_logger.error( "Fracture index computation failed.") + return False - pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName - pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, - self.m_attributeOnPoints ) + mask: npt.NDArray[ np.bool_ ] = np.argmin( np.abs( self.totalStress[ :, :2 ] ), axis=1 ) + horizontalStress: npt.NDArray[ np.float64 ] = self.totalStress[ :, :2 ][ np.arange( self.totalStress[ :, :2 ].shape[ 0 ] ), + mask ] - try: - assert stress is not None, ( f"{stressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert pressure is not None, ( f"{pressureAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert stress.shape[ 1 ] >= 3 - except AssertionError as e: - self.m_logger.error( "Critical total stress ratio and threshold were not computed due to:" ) - self.m_logger.error( str( e ) ) - return False + stressRatioThreshold: npt.NDArray[ np.float64 ] = fcts.totalStressRatioThreshold( self.pressure, horizontalStress ) + fractureThresholdAttributeName: str = PostProcessingOutputsEnum.TOTAL_STRESS_RATIO_THRESHOLD.attributeName + fractureThresholdOnPoints: bool = PostProcessingOutputsEnum.TOTAL_STRESS_RATIO_THRESHOLD.isOnPoints + if not createAttribute( self.output, + stressRatioThreshold, + fractureThresholdAttributeName, + onPoints=fractureThresholdOnPoints, + logger=self.m_logger ): + self.m_logger.error( "Fracture threshold computation failed.") + return False - # fracture index - try: - verticalStress: npt.NDArray[ np.float64 ] = stress[ :, 2 ] - criticalTotalStressRatio: npt.NDArray[ np.float64 ] = ( fcts.criticalTotalStressRatio( - pressure, verticalStress ) ) - createAttribute( - self.output, - criticalTotalStressRatio, - fractureIndexAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Critical total stress ratio was not computed due to:" ) - self.m_logger.error( str( e ) ) - ret = False - - # fracture threshold - try: - mask: npt.NDArray[ np.bool_ ] = np.argmin( np.abs( stress[ :, :2 ] ), axis=1 ) - horizontalStress: npt.NDArray[ np.float64 ] = stress[ :, :2 ][ np.arange( stress[ :, :2 ].shape[ 0 ] ), - mask ] - - stressRatioThreshold: npt.NDArray[ np.float64 ] = ( fcts.totalStressRatioThreshold( - pressure, horizontalStress ) ) - fractureThresholdAttributeName = ( - PostProcessingOutputsEnum.TOTAL_STRESS_RATIO_THRESHOLD.attributeName ) - createAttribute( - self.output, - stressRatioThreshold, - fractureThresholdAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Total stress ratio threshold was not computed due to:" ) - self.m_logger.error( str( e ) ) - ret = False - - return ret + return True def computeCriticalPorePressure( self: Self ) -> bool: """Compute the critical pore pressure and the pressure index. @@ -925,64 +886,30 @@ def computeCriticalPorePressure( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - ret: bool = True - criticalPorePressureAttributeName = ( PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE.attributeName ) - if not isAttributeInObject( self.output, criticalPorePressureAttributeName, self.m_attributeOnPoints ): - - stressAttributeName: str = ( PostProcessingOutputsEnum.STRESS_TOTAL.attributeName ) - stress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, stressAttributeName, - self.m_attributeOnPoints ) - - pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName - pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, - self.m_attributeOnPoints ) - - try: - assert self.m_rockCohesion is not None, "Rock cohesion is undefined." - assert self.m_frictionAngle is not None, "Friction angle is undefined." - assert stress is not None, ( f"{stressAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert pressure is not None, ( f"{pressureAttributeName}" + UNDEFINED_ATTRIBUTE_MESSAGE ) - assert stress.shape[ 1 ] >= 3 - except AssertionError as e: - self.m_logger.error( "Critical pore pressure and threshold were not computed due to:" ) - self.m_logger.error( str( e ) ) - return False - - try: - criticalPorePressure: npt.NDArray[ np.float64 ] = ( fcts.criticalPorePressure( - -1.0 * stress, self.m_rockCohesion, self.m_frictionAngle ) ) - createAttribute( - self.output, - criticalPorePressure, - criticalPorePressureAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Critical pore pressure was not computed due to:" ) - self.m_logger.error( str( e ) ) + criticalPorePressureAttributeName: str = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE.attributeName + criticalPorePressureOnPoints: bool = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE.isOnPoints + criticalPorePressure: npt.NDArray[ np.float64 ] = fcts.criticalPorePressure( -1.0 * self.totalStress, self.m_rockCohesion, self.m_frictionAngle ) + if not createAttribute( self.output, + criticalPorePressure, + criticalPorePressureAttributeName, + onPoints=criticalPorePressureOnPoints, + logger=self.m_logger ): + self.m_logger.error( "Critical pore pressure computation failed.") return False + + # add critical pore pressure index (i.e., ratio between pressure and criticalPorePressure) + criticalPorePressureIndex: npt.NDArray[ np.float64 ] = ( fcts.criticalPorePressureThreshold( self.pressure, criticalPorePressure ) ) + criticalPorePressureIndexAttributeName: str = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName + criticalPorePressureIndexOnPoint: bool = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.isOnPoints + if not createAttribute( self.output, + criticalPorePressureIndex, + criticalPorePressureIndexAttributeName, + onPoints=criticalPorePressureIndexOnPoint, + logger=self.m_logger ): + self.m_logger.error( "Critical pore pressure indexes computation failed.") + return False - try: - # add critical pore pressure index (i.e., ratio between pressure and criticalPorePressure) - assert criticalPorePressure is not None, ( "Critical pore pressure attribute" + " was not created" ) - criticalPorePressureIndex: npt.NDArray[ np.float64 ] = ( fcts.criticalPorePressureThreshold( - pressure, criticalPorePressure ) ) - criticalPorePressureIndexAttributeName = ( - PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName ) - createAttribute( - self.output, - criticalPorePressureIndex, - criticalPorePressureIndexAttributeName, - (), - self.m_attributeOnPoints, - ) - except AssertionError as e: - self.m_logger.error( "Pore pressure threshold was not computed due to:" ) - self.m_logger.error( str( e ) ) - ret = False - - return ret + return True def getPointCoordinates( self: Self ) -> npt.NDArray[ np.float64 ]: """Get the coordinates of Points or Cell center. From 8c5c28ab19b21836e415a668d1286f44802c47d7 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 23 Sep 2025 09:21:42 +0200 Subject: [PATCH 13/40] Add the function getOutput --- .../src/geos/mesh/processing/GeomechanicsCalculator.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index a582113f1..0823989d1 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -139,7 +139,7 @@ def __init__( def applyFilter( self: Self ) -> bool: - """Compute the geomechanical coefficient of the mesh. + """Compute the geomechanical outputs of the mesh. Returns: Boolean (bool): True if calculation successfully ended, False otherwise. @@ -156,6 +156,14 @@ def applyFilter( self: Self ) -> bool: return False return True + + def getOutput( self: Self ) -> Union[ vtkPointSet, vtkUnstructuredGrid ]: + """Get the mesh with the geomechanical outputs as attributes. + + Returns: + Union[vtkPointSet, vtkUnstructuredGrid]: The mesh with the geomechanical outputs as attributes. + """ + return self.output def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: """Set a specific handler for the filter logger. From f3667047d19206b950ed1ceba53ea8c7b589d4d7 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 23 Sep 2025 09:51:59 +0200 Subject: [PATCH 14/40] Clean for the ci --- .../mesh/processing/GeomechanicsCalculator.py | 280 ++++++++++-------- 1 file changed, 157 insertions(+), 123 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index 0823989d1..5dec5fad1 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file -from typing import Union +from typing import Union, Tuple import geos.geomechanics.processing.geomechanicsCalculatorFunctions as fcts @@ -12,7 +12,6 @@ from geos.utils.GeosOutputsConstants import ( AttributeEnum, - ComponentNameEnum, GeosMeshOutputsEnum, PostProcessingOutputsEnum, ) @@ -25,10 +24,7 @@ WATER_DENSITY, ) from typing_extensions import Self -from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase -from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector from vtkmodules.vtkCommonDataModel import ( - vtkDataSet, vtkPointSet, vtkUnstructuredGrid, ) @@ -90,9 +86,6 @@ output :Union[vtkPointSet, vtkUnstructuredGrid] = geomechanicsCalculatorFilter.GetOutputDataObject(0) """ -TYPE_ERROR_MESSAGE = ( "Input object must by either a vtkPointSet or a vtkUnstructuredGrid." ) -UNDEFINED_ATTRIBUTE_MESSAGE = " attribute is undefined." - loggerTitle: str = "Geomechanical Calculator Filter" @@ -109,25 +102,28 @@ def __init__( Args: mesh (Union[vtkPointsSet, vtkUnstructuredGrid]): Input mesh. computeAdvancedOutputs (bool, optional): True to compute advanced geomechanical parameters, False otherwise. - Defaults to False. + Defaults to False. speHandler (bool, optional): True to use a specific handler, False to use the internal handler. Defaults to False. """ + self.output: Union[ vtkPointSet, vtkUnstructuredGrid ] + if mesh.IsA( "vtkUnstructuredGrid" ): + self.output = vtkUnstructuredGrid() + elif mesh.IsA( "vtkPointSet" ): + self.output = vtkPointSet() - self.mesh: Union[ vtkPointSet, vtkUnstructuredGrid ] = mesh - self.output: Union[ vtkPointSet, vtkUnstructuredGrid ] = vtkDataSet() - self.output.DeepCopy( self.mesh ) + self.output.DeepCopy( mesh ) # additional parameters - self.m_computeAdvancedOutputs: bool = computeAdvancedOutputs - self.m_grainBulkModulus: float = DEFAULT_GRAIN_BULK_MODULUS - self.m_specificDensity: float = WATER_DENSITY - self.m_rockCohesion: float = DEFAULT_ROCK_COHESION - self.m_frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD + self.doComputeAdvancedOutputs: bool = computeAdvancedOutputs + self.grainBulkModulus: float = DEFAULT_GRAIN_BULK_MODULUS + self.specificDensity: float = WATER_DENSITY + self.rockCohesion: float = DEFAULT_ROCK_COHESION + self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD - # elastic moduli are either bulk and Shear moduli (m_computeYoungPoisson=True) - # or young Modulus and poisson's ratio (m_computeYoungPoisson=False) - self.m_computeYoungPoisson: bool = True + # elastic moduli are either bulk and Shear moduli (computeYoungPoisson=True) + # or young Modulus and poisson's ratio (computeYoungPoisson=False) + self.computeYoungPoisson: bool = True # Logger. self.m_logger: Logger @@ -137,7 +133,6 @@ def __init__( self.m_logger = logging.getLogger( loggerTitle ) self.m_logger.setLevel( logging.INFO ) - def applyFilter( self: Self ) -> bool: """Compute the geomechanical outputs of the mesh. @@ -151,12 +146,11 @@ def applyFilter( self: Self ) -> bool: if not self.computeBasicOutputs(): return False - if self.m_computeAdvancedOutputs: - if not self.computeAdvancedOutputs(): - return False + if self.doComputeAdvancedOutputs: + return self.computeAdvancedOutputs() return True - + def getOutput( self: Self ) -> Union[ vtkPointSet, vtkUnstructuredGrid ]: """Get the mesh with the geomechanical outputs as attributes. @@ -186,7 +180,7 @@ def setGrainBulkModulus( self: Self, grainBulkModulus: float ) -> None: Args: grainBulkModulus (float): Grain bulk modulus. """ - self.m_grainBulkModulus = grainBulkModulus + self.grainBulkModulus = grainBulkModulus def setSpecificDensity( self: Self, specificDensity: float ) -> None: """Set the specific density. @@ -194,7 +188,7 @@ def setSpecificDensity( self: Self, specificDensity: float ) -> None: Args: specificDensity (float): Specific density. """ - self.m_specificDensity = specificDensity + self.specificDensity = specificDensity def setRockCohesion( self: Self, rockCohesion: float ) -> None: """Set the rock cohesion. @@ -202,7 +196,7 @@ def setRockCohesion( self: Self, rockCohesion: float ) -> None: Args: rockCohesion (float): Rock cohesion. """ - self.m_rockCohesion = rockCohesion + self.rockCohesion = rockCohesion def setFrictionAngle( self: Self, frictionAngle: float ) -> None: """Set the friction angle. @@ -210,7 +204,7 @@ def setFrictionAngle( self: Self, frictionAngle: float ) -> None: Args: frictionAngle (float): Friction angle (rad) """ - self.m_frictionAngle = frictionAngle + self.frictionAngle = frictionAngle def getOutputType( self: Self ) -> str: """Get output object type. @@ -224,8 +218,8 @@ def checkMandatoryAttributes( self: Self ) -> bool: """Check that mandatory attributes are present in the mesh. The mesh must contains either the young Modulus and Poisson's ratio - (m_computeYoungPoisson=False) or the bulk and shear moduli - (m_computeYoungPoisson=True) + (computeYoungPoisson=False) or the bulk and shear moduli + (computeYoungPoisson=True) Returns: bool: True if all needed attributes are present, False otherwise @@ -235,11 +229,11 @@ def checkMandatoryAttributes( self: Self ) -> bool: self.youngModulusOnPoints: bool = PostProcessingOutputsEnum.YOUNG_MODULUS.isOnPoints self.poissonRatioOnPoints: bool = PostProcessingOutputsEnum.POISSON_RATIO.isOnPoints - self.m_computeYoungPoisson = not isAttributeInObject( self.output, self.youngModulusAttributeName, self.youngModulusOnPoints ) \ + self.computeYoungPoisson = not isAttributeInObject( self.output, self.youngModulusAttributeName, self.youngModulusOnPoints ) \ or not isAttributeInObject( self.output, self.poissonRatioAttributeName, self.poissonRatioOnPoints ) # if none of elastic moduli is present, return False - if self.m_computeYoungPoisson: + if self.computeYoungPoisson: self.bulkModulusAttributeName: str = GeosMeshOutputsEnum.BULK_MODULUS.attributeName self.shearModulusAttributeName: str = GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName self.bulkModulusOnPoints: str = GeosMeshOutputsEnum.BULK_MODULUS.isOnPoints @@ -248,73 +242,87 @@ def checkMandatoryAttributes( self: Self ) -> bool: if not isAttributeInObject( self.output, self.bulkModulusAttributeName, self.bulkModulusOnPoints ) \ or not isAttributeInObject( self.output, self.shearModulusAttributeName, self.shearModulusOnPoints ): mess: str = ( "Mandatory properties are missing to compute geomechanical outputs:" ) - mess += ( f"Either { self.bulkModulusAttributeName } and { self.shearModulusAttributeName } or { self.youngModulusAttributeName } and { self.poissonRatioAttributeName } must be present in the table data." ) + mess += ( + f"Either { self.bulkModulusAttributeName } and { self.shearModulusAttributeName } or { self.youngModulusAttributeName } and { self.poissonRatioAttributeName } must be present in the table data." + ) self.m_logger.error( mess ) return False - + porosityAttributeName: str = GeosMeshOutputsEnum.POROSITY.attributeName porosityOnPoints: bool = GeosMeshOutputsEnum.POROSITY.isOnPoints if not isAttributeInObject( self.output, porosityAttributeName, porosityOnPoints ): - self.m_logger.error( f"The mandatory attribute { porosityAttributeName } is missing to compute geomechanical outputs." ) + self.m_logger.error( + f"The mandatory attribute { porosityAttributeName } is missing to compute geomechanical outputs." ) return False else: - self.porosity: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityAttributeName, porosityOnPoints ) + self.porosity: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityAttributeName, + porosityOnPoints ) porosityInitialAttributeName: str = GeosMeshOutputsEnum.POROSITY_INI.attributeName porosityInitialOnPoints: bool = GeosMeshOutputsEnum.POROSITY_INI.isOnPoints if not isAttributeInObject( self.output, porosityInitialAttributeName, porosityInitialOnPoints ): - self.m_logger.error( f"The mandatory attribute { porosityInitialAttributeName } is missing to compute geomechanical outputs." ) + self.m_logger.error( + f"The mandatory attribute { porosityInitialAttributeName } is missing to compute geomechanical outputs." + ) return False else: - self.porosityInitial: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityInitialAttributeName, porosityInitialOnPoints ) + self.porosityInitial: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, + porosityInitialAttributeName, + porosityInitialOnPoints ) deltaPressureAttributeName: str = GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName deltaPressureOnPoint: bool = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints if not isAttributeInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ): - self.m_logger.error( f"The mandatory attribute { deltaPressureAttributeName } is missing to compute geomechanical outputs." ) + self.m_logger.error( + f"The mandatory attribute { deltaPressureAttributeName } is missing to compute geomechanical outputs." ) return False else: - self.deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ) + self.deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, + deltaPressureOnPoint ) densityAttributeName: str = GeosMeshOutputsEnum.ROCK_DENSITY.attributeName densityOnPoints: bool = GeosMeshOutputsEnum.ROCK_DENSITY.isOnPoints if not isAttributeInObject( self.output, densityAttributeName, densityOnPoints ): - self.m_logger.error( f"The mandatory attribute { densityAttributeName } is missing to compute geomechanical outputs." ) + self.m_logger.error( + f"The mandatory attribute { densityAttributeName } is missing to compute geomechanical outputs." ) return False else: - self.density: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, densityAttributeName, densityOnPoints ) + self.density: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, densityAttributeName, + densityOnPoints ) effectiveStressAttributeName: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName effectiveStressOnPoints: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.isOnPoints if not isAttributeInObject( self.output, effectiveStressAttributeName, effectiveStressOnPoints ): - self.m_logger.error( f"The mandatory attribute { effectiveStressAttributeName } is missing to compute geomechanical outputs." ) + self.m_logger.error( + f"The mandatory attribute { effectiveStressAttributeName } is missing to compute geomechanical outputs." + ) return False else: - self.effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressAttributeName, effectiveStressOnPoints ) + self.effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, + effectiveStressAttributeName, + effectiveStressOnPoints ) effectiveStressT0AttributeName: str = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.attributeName effectiveStressT0OnPoints: bool = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.isOnPoints if not isAttributeInObject( self.output, effectiveStressT0AttributeName, effectiveStressT0OnPoints ): - self.m_logger.error( f"The mandatory attribute { effectiveStressT0AttributeName } is missing to compute geomechanical outputs." ) + self.m_logger.error( + f"The mandatory attribute { effectiveStressT0AttributeName } is missing to compute geomechanical outputs." + ) return False else: - self.effectiveStressT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, effectiveStressT0AttributeName, effectiveStressT0OnPoints ) + self.effectiveStressT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, + effectiveStressT0AttributeName, + effectiveStressT0OnPoints ) pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName pressureOnPoints: bool = GeosMeshOutputsEnum.PRESSURE.isOnPoints if not isAttributeInObject( self.output, pressureAttributeName, pressureOnPoints ): - self.m_logger.error( f"The mandatory attribute { pressureAttributeName } is missing to compute geomechanical outputs." ) + self.m_logger.error( + f"The mandatory attribute { pressureAttributeName } is missing to compute geomechanical outputs." ) return False else: - self.pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, pressureOnPoints ) - - deltaPressureAttributeName: str = GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName - deltaPressureOnPoint: bool = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints - if not isAttributeInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ): - self.m_logger.error( f"The mandatory attribute { deltaPressureAttributeName } is missing to compute geomechanical outputs." ) - return False - else: - self.deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ) + self.pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, + pressureOnPoints ) return True @@ -340,7 +348,7 @@ def computeBasicOutputs( self: Self ) -> bool: return False if not self.computeSpecificGravity(): - self.m_logger.error( "Specific gravity computation failed.") + self.m_logger.error( "Specific gravity computation failed." ) return False # TODO: deactivate lithostatic stress calculation until right formula @@ -378,7 +386,7 @@ def computeAdvancedOutputs( self: Self ) -> bool: """ if not self.computeCriticalTotalStressRatio(): return False - + if not self.computeCriticalPorePressure(): return False @@ -400,7 +408,7 @@ def computeElasticModulus( self: Self ) -> bool: self.shearModulus: npt.NDArray[ np.float64 ] self.youngModulus: npt.NDArray[ np.float64 ] self.poissonRatio: npt.NDArray[ np.float64 ] - if self.m_computeYoungPoisson: + if self.computeYoungPoisson: return self.computeElasticModulusFromBulkShear() return self.computeElasticModulusFromYoungPoisson() @@ -417,7 +425,7 @@ def computeElasticModulusFromBulkShear( self: Self ) -> bool: if np.any( self.youngModulus < 0 ): self.m_logger.error( "Young modulus yields negative values. Check Bulk and Shear modulus values." ) return False - + if not createAttribute( self.output, self.youngModulus, self.youngModulusAttributeName, @@ -428,7 +436,7 @@ def computeElasticModulusFromBulkShear( self: Self ) -> bool: self.poissonRatio = fcts.poissonRatio( self.bulkModulus, self.shearModulus ) if np.any( self.poissonRatio < 0 ): - self.m_logger.error( "Poisson ratio yields negative values. Check Bulk and Shear modulus values.") + self.m_logger.error( "Poisson ratio yields negative values. Check Bulk and Shear modulus values." ) return False if not createAttribute( self.output, @@ -449,12 +457,12 @@ def computeElasticModulusFromYoungPoisson( self: Self ) -> bool: """ self.youngModulus = getArrayInObject( self.output, self.youngModulusAttributeName, self.youngModulusOnPoints ) self.poissonRatio = getArrayInObject( self.output, self.poissonRatioAttributeName, self.poissonRatioOnPoints ) - + self.bulkModulus = fcts.bulkModulus( self.youngModulus, self.poissonRatio ) if np.any( self.bulkModulus < 0 ): - self.m_logger.error( "Bulk modulus yields negative values. Check Young modulus and Poisson ratio values.") + self.m_logger.error( "Bulk modulus yields negative values. Check Young modulus and Poisson ratio values." ) return False - + if not createAttribute( self.output, self.bulkModulus, self.bulkModulusAttributeName, @@ -462,12 +470,12 @@ def computeElasticModulusFromYoungPoisson( self: Self ) -> bool: logger=self.m_logger ): self.m_logger.error( "Bulk modulus computation failed." ) return False - + self.shearModulus = fcts.shearModulus( self.youngModulus, self.poissonRatio ) if np.any( self.shearModulus < 0 ): - self.m_logger.error( "Shear modulus yields negative values. Check Young modulus and Poisson ratio values.") + self.m_logger.error( "Shear modulus yields negative values. Check Young modulus and Poisson ratio values." ) return False - + if not createAttribute( self.output, self.shearModulus, self.shearModulusAttributeName, @@ -475,7 +483,7 @@ def computeElasticModulusFromYoungPoisson( self: Self ) -> bool: logger=self.m_logger ): self.m_logger.error( "Shear modulus computation failed." ) return False - + return True def computeBiotCoefficient( self: Self ) -> bool: @@ -486,7 +494,8 @@ def computeBiotCoefficient( self: Self ) -> bool: """ biotCoefficientAttributeName: str = PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName biotCoefficientOnPoints: bool = PostProcessingOutputsEnum.BIOT_COEFFICIENT.isOnPoints - self.biotCoefficient: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.m_grainBulkModulus, self.bulkModulus ) + self.biotCoefficient: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.grainBulkModulus, + self.bulkModulus ) return createAttribute( self.output, self.biotCoefficient, biotCoefficientAttributeName, @@ -517,7 +526,8 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: # oedometric compressibility compressibilityOedAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY_OED.attributeName compressibilityOedOnPoints: bool = PostProcessingOutputsEnum.COMPRESSIBILITY_OED.isOnPoints - compressibilityOed: npt.NDArray[ np.float64 ] = fcts.compressibilityOed( self.shearModulus, self.bulkModulus, self.porosity ) + compressibilityOed: npt.NDArray[ np.float64 ] = fcts.compressibilityOed( self.shearModulus, self.bulkModulus, + self.porosity ) if not createAttribute( self.output, compressibilityOed, compressibilityOedAttributeName, @@ -529,13 +539,14 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: # real compressibility compressibilityRealAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY_REAL.attributeName compressibilityRealOnPoint: bool = PostProcessingOutputsEnum.COMPRESSIBILITY_REAL.isOnPoints - compressibilityReal: npt.NDArray[ np.float64 ] = fcts.compressibilityReal( self.deltaPressure, self.porosity, self.porosityInitial ) + compressibilityReal: npt.NDArray[ np.float64 ] = fcts.compressibilityReal( self.deltaPressure, self.porosity, + self.porosityInitial ) if not createAttribute( self.output, compressibilityReal, compressibilityRealAttributeName, onPoints=compressibilityRealOnPoint, logger=self.m_logger ): - self.m_logger.error( "Real compressibility coefficient computation failed.") + self.m_logger.error( "Real compressibility coefficient computation failed." ) return False return True @@ -551,7 +562,7 @@ def computeSpecificGravity( self: Self ) -> bool: """ specificGravityAttributeName: str = PostProcessingOutputsEnum.SPECIFIC_GRAVITY.attributeName specificGravityOnPoints: bool = PostProcessingOutputsEnum.SPECIFIC_GRAVITY.isOnPoints - specificGravity: npt.NDArray[ np.float64 ] = fcts.specificGravity( self.density, self.m_specificDensity ) + specificGravity: npt.NDArray[ np.float64 ] = fcts.specificGravity( self.density, self.specificDensity ) return createAttribute( self.output, specificGravity, specificGravityAttributeName, @@ -564,7 +575,8 @@ def computeRealEffectiveStressRatio( self: Self ) -> bool: Returns: bool: True if calculation successfully ended, False otherwise. """ - return self.computeStressRatioReal( self.effectiveStress, PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_REAL ) + return self.computeStressRatioReal( self.effectiveStress, + PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_REAL ) def computeTotalStresses( self: Self ) -> bool: """Compute total stress total stress ratio. @@ -577,30 +589,31 @@ def computeTotalStresses( self: Self ) -> bool: """ # Compute total stress at initial time step. if not self.computeTotalStressInitial(): - self.m_logger.error( "Total stress at initial time step computation failed.") + self.m_logger.error( "Total stress at initial time step computation failed." ) return False # Compute total stress at current time step. totalStressAttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL.attributeName totalStressOnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL.isOnPoints - totalStressComponentNames: tuple[ str, ...] = tuple() + totalStressComponentNames: Tuple[ str, ...] = () if PostProcessingOutputsEnum.STRESS_TOTAL.nbComponent > 1: totalStressComponentNames = getComponentNames( self.output, totalStressAttributeName, totalStressOnPoints ) - self.totalStress: npt.NDArray[ np.float64 ] = self.doComputeTotalStress( self.effectiveStress, self.pressure, self.biotCoefficient ) + self.totalStress: npt.NDArray[ np.float64 ] = self.doComputeTotalStress( self.effectiveStress, self.pressure, + self.biotCoefficient ) if not createAttribute( self.output, self.totalStress, totalStressAttributeName, componentNames=totalStressComponentNames, onPoints=totalStressOnPoints, logger=self.m_logger ): - self.m_logger.error( "Total stress at current time step computation failed.") + self.m_logger.error( "Total stress at current time step computation failed." ) return False - + # Compute total stress ratio. if not self.computeStressRatioReal( self.totalStress, PostProcessingOutputsEnum.STRESS_TOTAL_RATIO_REAL ): - self.m_logger.error( "Total stress ratio computation failed.") + self.m_logger.error( "Total stress ratio computation failed." ) return False - + return True def computeTotalStressInitial( self: Self ) -> bool: @@ -616,21 +629,23 @@ def computeTotalStressInitial( self: Self ) -> bool: bulkModulusT0OnPoints: bool = PostProcessingOutputsEnum.BULK_MODULUS_INITIAL.isOnPoints youngModulusT0OnPoints: bool = PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.isOnPoints poissonRatioT0OnPoints: bool = PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.isOnPoints - + # Compute BulkModulus at initial time step. bulkModulusT0: npt.NDArray[ np.float64 ] if isAttributeInObject( self.output, bulkModulusT0AttributeName, bulkModulusT0OnPoints ): bulkModulusT0 = getArrayInObject( self.output, bulkModulusT0AttributeName, bulkModulusT0OnPoints ) elif isAttributeInObject( self.output, youngModulusT0AttributeName, youngModulusT0OnPoints ) \ and isAttributeInObject( self.output, poissonRatioT0AttributeName, poissonRatioT0OnPoints ): - youngModulusT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, youngModulusT0AttributeName, youngModulusT0OnPoints ) - poissonRatioT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioT0AttributeName, poissonRatioT0OnPoints ) + youngModulusT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, youngModulusT0AttributeName, + youngModulusT0OnPoints ) + poissonRatioT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioT0AttributeName, + poissonRatioT0OnPoints ) bulkModulusT0 = fcts.bulkModulus( youngModulusT0, poissonRatioT0 ) else: self.m_logger( "Elastic moduli at initial time are absent." ) # Compute Biot at initial time step. - biotCoefficientT0: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.m_grainBulkModulus, bulkModulusT0 ) + biotCoefficientT0: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.grainBulkModulus, bulkModulusT0 ) pressureT0: npt.NDArray[ np.float64 ] # Case pressureT0 is None, total stress = effective stress @@ -641,9 +656,10 @@ def computeTotalStressInitial( self: Self ) -> bool: totalStressT0AttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.attributeName totalStressT0OnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.isOnPoints - totalStressT0ComponentNames: tuple[ str, ...] = tuple() + totalStressT0ComponentNames: Tuple[ str, ...] = () if PostProcessingOutputsEnum.STRESS_TOTAL.nbComponent > 1: - totalStressT0ComponentNames = getComponentNames( self.output, totalStressT0AttributeName, totalStressT0OnPoints ) + totalStressT0ComponentNames = getComponentNames( self.output, totalStressT0AttributeName, + totalStressT0OnPoints ) self.totalStressT0 = self.doComputeTotalStress( self.effectiveStressT0, pressureT0, biotCoefficientT0 ) return createAttribute( self.output, self.totalStressT0, @@ -690,13 +706,14 @@ def computeLithostaticStress( self: Self ) -> bool: lithostaticStressAttributeName: str = PostProcessingOutputsEnum.LITHOSTATIC_STRESS.attributeName lithostaticStressOnPoint: bool = PostProcessingOutputsEnum.LITHOSTATIC_STRESS.isOnPoints - depth: npt.NDArray[ np.float64 ] = self.computeDepthAlongLine() if lithostaticStressOnPoint else self.computeDepthInMesh() + depth: npt.NDArray[ + np.float64 ] = self.computeDepthAlongLine() if lithostaticStressOnPoint else self.computeDepthInMesh() lithostaticStress = fcts.lithostaticStress( depth, self.density, GRAVITY ) return createAttribute( self.output, lithostaticStress, lithostaticStressAttributeName, onPoints=lithostaticStressOnPoint, - logger=self.m_logger) + logger=self.m_logger ) def computeDepthAlongLine( self: Self ) -> npt.NDArray[ np.float64 ]: """Compute depth along a line. @@ -705,7 +722,7 @@ def computeDepthAlongLine( self: Self ) -> npt.NDArray[ np.float64 ]: npt.NDArray[np.float64]: 1D array with depth property """ # get z coordinate - zCoord: npt.NDArray[ np.float64 ] = self.getZcoordinates() + zCoord: npt.NDArray[ np.float64 ] = self.getZcoordinates( True ) assert zCoord is not None, "Depth coordinates cannot be computed." # TODO: to find how to compute depth in a general case @@ -720,22 +737,25 @@ def computeDepthInMesh( self: Self ) -> npt.NDArray[ np.float64 ]: npt.NDArray[np.float64]: array with depth property """ # get z coordinate - zCoord: npt.NDArray[ np.float64 ] = self.getZcoordinates() + zCoord: npt.NDArray[ np.float64 ] = self.getZcoordinates( False ) assert zCoord is not None, "Depth coordinates cannot be computed." # TODO: to find how to compute depth in a general case depth: npt.NDArray[ np.float64 ] = -1.0 * zCoord return depth - def getZcoordinates( self: Self ) -> npt.NDArray[ np.float64 ]: + def getZcoordinates( self: Self, onPoints: bool ) -> npt.NDArray[ np.float64 ]: """Get z coordinates from self.output. + Args: + onPoints (bool): True if the attribute is on points, False if it is on cells. + Returns: npt.NDArray[np.float64]: 1D array with depth property """ # get z coordinate zCoord: npt.NDArray[ np.float64 ] - pointCoords: npt.NDArray[ np.float64 ] = self.getPointCoordinates() + pointCoords: npt.NDArray[ np.float64 ] = self.getPointCoordinates( onPoints ) assert pointCoords is not None, "Point coordinates are undefined." assert pointCoords.shape[ 1 ] == 2, "Point coordinates are undefined." zCoord = pointCoords[ :, 2 ] @@ -751,14 +771,16 @@ def computeElasticStrain( self: Self ) -> bool: elasticStrainAttributeName: str = PostProcessingOutputsEnum.STRAIN_ELASTIC.attributeName elasticStrainOnPoints: bool = PostProcessingOutputsEnum.STRAIN_ELASTIC.isOnPoints - elasticStrainComponentNames: tuple[ str, ...] = tuple() + elasticStrainComponentNames: Tuple[ str, ...] = () if PostProcessingOutputsEnum.STRESS_TOTAL.nbComponent > 1: - elasticStrainComponentNames = getComponentNames( self.output, elasticStrainAttributeName, elasticStrainOnPoints ) - - if self.m_computeYoungPoisson: - elasticStrain: npt.NDArray[ np.float64 ] = fcts.elasticStrainFromBulkShear( deltaEffectiveStress, self.bulkModulus, self.shearModulus ) + elasticStrainComponentNames = getComponentNames( self.output, elasticStrainAttributeName, + elasticStrainOnPoints ) + elasticStrain: npt.NDArray[ np.float64 ] + if self.computeYoungPoisson: + elasticStrain = fcts.elasticStrainFromBulkShear( deltaEffectiveStress, self.bulkModulus, self.shearModulus ) else: - elasticStrain: npt.NDArray[ np.float64 ] = fcts.elasticStrainFromYoungPoisson( deltaEffectiveStress, self.youngModulus, self.poissonRatio ) + elasticStrain = fcts.elasticStrainFromYoungPoisson( deltaEffectiveStress, self.youngModulus, + self.poissonRatio ) return createAttribute( self.output, elasticStrain, @@ -771,14 +793,15 @@ def computeReservoirStressPathReal( self: Self ) -> bool: """Compute reservoir stress paths. Returns: - bool: return True if calculation successfully ended, False otherwise. + bool: True if calculation successfully ended, False otherwise. """ # create delta stress attribute for QC deltaTotalStressAttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.attributeName deltaTotalStressOnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.isOnPoints - deltaTotalStressComponentNames: tuple[ str, ...] = tuple() - if PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.nbComponent > 1 : - deltaTotalStressComponentNames = getComponentNames( self.output, deltaTotalStressAttributeName, deltaTotalStressOnPoints ) + deltaTotalStressComponentNames: Tuple[ str, ...] = () + if PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.nbComponent > 1: + deltaTotalStressComponentNames = getComponentNames( self.output, deltaTotalStressAttributeName, + deltaTotalStressOnPoints ) self.deltaTotalStress: npt.NDArray[ np.float64 ] = self.totalStress - self.totalStressT0 if not createAttribute( self.output, self.deltaTotalStress, @@ -786,15 +809,16 @@ def computeReservoirStressPathReal( self: Self ) -> bool: componentNames=deltaTotalStressComponentNames, onPoints=deltaTotalStressOnPoints, logger=self.m_logger ): - self.m_logger.error( "Delta total stress computation failed.") + self.m_logger.error( "Delta total stress computation failed." ) return False rspRealAttributeName: str = PostProcessingOutputsEnum.RSP_REAL.attributeName rspRealOnPoints: bool = PostProcessingOutputsEnum.RSP_REAL.isOnPoints - rspRealComponentNames: tuple[ str, ...] = tuple() - if PostProcessingOutputsEnum.RSP_REAL.nbComponent > 1 : + rspRealComponentNames: Tuple[ str, ...] = () + if PostProcessingOutputsEnum.RSP_REAL.nbComponent > 1: rspRealComponentNames = getComponentNames( self.output, rspRealAttributeName, rspRealOnPoints ) - self.rspReal: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathReal( self.deltaTotalStress, self.deltaPressure ) + self.rspReal: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathReal( self.deltaTotalStress, + self.deltaPressure ) if not createAttribute( self.output, self.rspReal, rspRealAttributeName, @@ -804,6 +828,8 @@ def computeReservoirStressPathReal( self: Self ) -> bool: self.m_logger.error( "Reservoir stress real path computation failed." ) return False + return True + def computeReservoirStressPathOed( self: Self ) -> bool: """Compute Reservoir Stress Path in oedometric conditions. @@ -836,7 +862,7 @@ def computeStressRatioReal( self: Self, stress: npt.NDArray[ np.float64 ], outpu self.stressRatioReal, stressRatioRealAttributeName, onPoints=stressRatioRealOnPoints, - logger=self.m_logger) + logger=self.m_logger ) def computeEffectiveStressRatioOed( self: Self ) -> bool: """Compute the effective stress ratio in oedometric conditions. @@ -862,20 +888,22 @@ def computeCriticalTotalStressRatio( self: Self ) -> bool: fractureIndexAttributeName: str = PostProcessingOutputsEnum.CRITICAL_TOTAL_STRESS_RATIO.attributeName fractureIndexOnPoints: bool = PostProcessingOutputsEnum.CRITICAL_TOTAL_STRESS_RATIO.isOnPoints verticalStress: npt.NDArray[ np.float64 ] = self.totalStress[ :, 2 ] - criticalTotalStressRatio: npt.NDArray[ np.float64 ] = fcts.criticalTotalStressRatio( self.pressure, verticalStress ) + criticalTotalStressRatio: npt.NDArray[ np.float64 ] = fcts.criticalTotalStressRatio( + self.pressure, verticalStress ) if not createAttribute( self.output, criticalTotalStressRatio, fractureIndexAttributeName, onPoints=fractureIndexOnPoints, logger=self.m_logger ): - self.m_logger.error( "Fracture index computation failed.") + self.m_logger.error( "Fracture index computation failed." ) return False mask: npt.NDArray[ np.bool_ ] = np.argmin( np.abs( self.totalStress[ :, :2 ] ), axis=1 ) - horizontalStress: npt.NDArray[ np.float64 ] = self.totalStress[ :, :2 ][ np.arange( self.totalStress[ :, :2 ].shape[ 0 ] ), - mask ] + horizontalStress: npt.NDArray[ np.float64 ] = self.totalStress[ :, :2 ][ + np.arange( self.totalStress[ :, :2 ].shape[ 0 ] ), mask ] - stressRatioThreshold: npt.NDArray[ np.float64 ] = fcts.totalStressRatioThreshold( self.pressure, horizontalStress ) + stressRatioThreshold: npt.NDArray[ np.float64 ] = fcts.totalStressRatioThreshold( + self.pressure, horizontalStress ) fractureThresholdAttributeName: str = PostProcessingOutputsEnum.TOTAL_STRESS_RATIO_THRESHOLD.attributeName fractureThresholdOnPoints: bool = PostProcessingOutputsEnum.TOTAL_STRESS_RATIO_THRESHOLD.isOnPoints if not createAttribute( self.output, @@ -883,7 +911,7 @@ def computeCriticalTotalStressRatio( self: Self ) -> bool: fractureThresholdAttributeName, onPoints=fractureThresholdOnPoints, logger=self.m_logger ): - self.m_logger.error( "Fracture threshold computation failed.") + self.m_logger.error( "Fracture threshold computation failed." ) return False return True @@ -896,17 +924,20 @@ def computeCriticalPorePressure( self: Self ) -> bool: """ criticalPorePressureAttributeName: str = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE.attributeName criticalPorePressureOnPoints: bool = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE.isOnPoints - criticalPorePressure: npt.NDArray[ np.float64 ] = fcts.criticalPorePressure( -1.0 * self.totalStress, self.m_rockCohesion, self.m_frictionAngle ) + criticalPorePressure: npt.NDArray[ np.float64 ] = fcts.criticalPorePressure( -1.0 * self.totalStress, + self.rockCohesion, + self.frictionAngle ) if not createAttribute( self.output, criticalPorePressure, criticalPorePressureAttributeName, onPoints=criticalPorePressureOnPoints, logger=self.m_logger ): - self.m_logger.error( "Critical pore pressure computation failed.") - return False - + self.m_logger.error( "Critical pore pressure computation failed." ) + return False + # add critical pore pressure index (i.e., ratio between pressure and criticalPorePressure) - criticalPorePressureIndex: npt.NDArray[ np.float64 ] = ( fcts.criticalPorePressureThreshold( self.pressure, criticalPorePressure ) ) + criticalPorePressureIndex: npt.NDArray[ np.float64 ] = ( fcts.criticalPorePressureThreshold( + self.pressure, criticalPorePressure ) ) criticalPorePressureIndexAttributeName: str = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName criticalPorePressureIndexOnPoint: bool = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.isOnPoints if not createAttribute( self.output, @@ -914,18 +945,21 @@ def computeCriticalPorePressure( self: Self ) -> bool: criticalPorePressureIndexAttributeName, onPoints=criticalPorePressureIndexOnPoint, logger=self.m_logger ): - self.m_logger.error( "Critical pore pressure indexes computation failed.") + self.m_logger.error( "Critical pore pressure indexes computation failed." ) return False return True - def getPointCoordinates( self: Self ) -> npt.NDArray[ np.float64 ]: + def getPointCoordinates( self: Self, onPoints: bool ) -> npt.NDArray[ np.float64 ]: """Get the coordinates of Points or Cell center. + Args: + onPoints (bool): True if the attribute is on points, False if it is on cells. + Returns: npt.NDArray[np.float64]: points/cell center coordinates """ - if self.m_attributeOnPoints: + if onPoints: return self.output.GetPoints() # type: ignore[no-any-return] else: # Find cell centers From c227278be1f6a81ea40d372a90f99576e212f506 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 23 Sep 2025 10:24:53 +0200 Subject: [PATCH 15/40] update the doc --- .../mesh/processing/GeomechanicsCalculator.py | 114 +++++++++--------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index 5dec5fad1..647bd4eee 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -1,21 +1,32 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. -# SPDX-FileContributor: Martin Lemay +# SPDX-FileContributor: Martin Lemay, Romain Baville # ruff: noqa: E402 # disable Module level import not at top of file from typing import Union, Tuple +from typing_extensions import Self -import geos.geomechanics.processing.geomechanicsCalculatorFunctions as fcts - +import logging import numpy as np import numpy.typing as npt -import logging +import geos.geomechanics.processing.geomechanicsCalculatorFunctions as fcts + +from geos.mesh.utils.arrayModifiers import createAttribute +from geos.mesh.utils.arrayHelpers import ( + getArrayInObject, + getComponentNames, + isAttributeInObject, +) + +from geos.utils.Logger import ( + Logger, + getLogger, +) from geos.utils.GeosOutputsConstants import ( AttributeEnum, GeosMeshOutputsEnum, PostProcessingOutputsEnum, ) -from geos.utils.Logger import Logger, getLogger from geos.utils.PhysicalConstants import ( DEFAULT_FRICTION_ANGLE_RAD, DEFAULT_GRAIN_BULK_MODULUS, @@ -23,25 +34,23 @@ GRAVITY, WATER_DENSITY, ) -from typing_extensions import Self -from vtkmodules.vtkCommonDataModel import ( - vtkPointSet, - vtkUnstructuredGrid, -) + +from vtkmodules.vtkCommonDataModel import vtkPointSet, vtkUnstructuredGrid from vtkmodules.vtkFiltersCore import vtkCellCenters -from geos.mesh.utils.arrayModifiers import createAttribute -from geos.mesh.utils.arrayHelpers import ( - getArrayInObject, - getComponentNames, - isAttributeInObject, -) __doc__ = """ GeomechanicsCalculator module is a vtk filter that allows to compute additional Geomechanical properties from existing ones. -GeomechanicsCalculator filter inputs are either vtkPointSet or vtkUnstructuredGrid -and returned object is of same type as input. +GeomechanicsCalculator filter input mesh is either vtkPointSet or vtkUnstructuredGrid +and returned mesh is of same type as input. + +.. Note:: + - The default physical constants used by the filter are: + - grainBulkModulus = 38e9 Pa (the one of the Quartz) + - specificDensity = 1000.0 kg/m³ (the one of the water) + - rockCohesion = 0.0 Pa + - frictionAngle = 10.0 / 180.0 * np.pi rad To use the filter: @@ -50,40 +59,35 @@ import numpy as np from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator - # filter inputs - logger :Logger - # input object - input :Union[vtkPointSet, vtkUnstructuredGrid] - # grain bulk modulus in Pa (e.g., use a very high value to get a Biot coefficient equal to 1) - grainBulkModulus :float = 1e26 - # Reference density to compute specific gravity (e.g. fresh water density) in kg.m^-3 - specificDensity :float = 1000. - # rock cohesion in Pa - rockCohesion :float = 1e8 - # friction angle in ° - frictionAngle :float = 10 * np.pi / 180. - - # instantiate the filter - geomechanicsCalculatorFilter :GeomechanicsCalculator = GeomechanicsCalculator() - - # set filter attributes - # set logger - geomechanicsCalculatorFilter.SetLogger(logger) - # set input object - geomechanicsCalculatorFilter.SetInputDataObject(input) - # set computeAdvancedOutputsOn or computeAdvancedOutputsOff to compute or - # not advanced outputs... - geomechanicsCalculatorFilter.computeAdvancedOutputsOn() - # set other parameters - geomechanicsCalculatorFilter.SetGrainBulkModulus(grainBulkModulus) - geomechanicsCalculatorFilter.SetSpecificDensity(specificDensity) - # rock cohesion and friction angle are used for advanced outputs only - geomechanicsCalculatorFilter.SetRockCohesion(rockCohesion) - geomechanicsCalculatorFilter.SetFrictionAngle(frictionAngle) - # do calculations - geomechanicsCalculatorFilter.Update() - # get filter output (same type as input) - output :Union[vtkPointSet, vtkUnstructuredGrid] = geomechanicsCalculatorFilter.GetOutputDataObject(0) + # Define filter inputs + mesh: Union[ vtkPointSet, vtkUnstructuredGrid ] + computeAdvancedOutputs: bool # optional, defaults to False + speHandler: bool # optional, defaults to False + + # Instantiate the filter + filter: GeomechanicsCalculator = GeomechanicsCalculator( mesh, computeAdvancedOutputs, speHandler ) + + # Use your own handler (if speHandler is True) + yourHandler: logging.Handler + filter.setLoggerHandler( yourHandler ) + + # Change the physical constants if needed + grainBulkModulus: float + specificDensity: float + rockCohesion: float + frictionAngle: float + + filter.SetGrainBulkModulus(grainBulkModulus) + filter.SetSpecificDensity(specificDensity) + filter.SetRockCohesion(rockCohesion) + filter.SetFrictionAngle(frictionAngle) + + # Do calculations + filter.applyFilter() + + # Get the mesh with the geomechanical output as attribute + output :Union[vtkPointSet, vtkUnstructuredGrid] + output = filter.getOutput() """ loggerTitle: str = "Geomechanical Calculator Filter" @@ -111,17 +115,17 @@ def __init__( self.output = vtkUnstructuredGrid() elif mesh.IsA( "vtkPointSet" ): self.output = vtkPointSet() - self.output.DeepCopy( mesh ) - # additional parameters self.doComputeAdvancedOutputs: bool = computeAdvancedOutputs + + # Defaults physical constants self.grainBulkModulus: float = DEFAULT_GRAIN_BULK_MODULUS self.specificDensity: float = WATER_DENSITY self.rockCohesion: float = DEFAULT_ROCK_COHESION self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD - # elastic moduli are either bulk and Shear moduli (computeYoungPoisson=True) + # Elastic moduli are either bulk and Shear moduli (computeYoungPoisson=True) # or young Modulus and poisson's ratio (computeYoungPoisson=False) self.computeYoungPoisson: bool = True From f54221b41c22adf133aa72beb32b250ac2f0af21 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 23 Sep 2025 11:10:44 +0200 Subject: [PATCH 16/40] Change the name to add logic --- .../{PVGeomechanicsAnalysis.py => PVGeomechanicsCalculator.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename geos-pv/src/geos/pv/plugins/{PVGeomechanicsAnalysis.py => PVGeomechanicsCalculator.py} (100%) diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsAnalysis.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py similarity index 100% rename from geos-pv/src/geos/pv/plugins/PVGeomechanicsAnalysis.py rename to geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py From be176c94130347756c6b5e7ba8df84bca22dc8da Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 23 Sep 2025 11:11:06 +0200 Subject: [PATCH 17/40] refactor the doc --- docs/geos_mesh_docs/processing.rst | 7 +++++++ docs/geos_posp_docs/PVplugins.rst | 7 ------- docs/geos_posp_docs/filters.rst | 8 -------- docs/geos_pv_docs/processing.rst | 6 ++++++ 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/geos_mesh_docs/processing.rst b/docs/geos_mesh_docs/processing.rst index 003cc973d..79eac2ba9 100644 --- a/docs/geos_mesh_docs/processing.rst +++ b/docs/geos_mesh_docs/processing.rst @@ -19,6 +19,13 @@ geos.mesh.processing.FillPartialArrays filter :undoc-members: :show-inheritance: +geos.mesh.processing.GeomechanicsCalculator module +--------------------------------------------------- + +.. automodule:: geos.mesh.processing.GeomechanicsCalculator + :members: + :undoc-members: + :show-inheritance: geos.mesh.processing.meshQualityMetricHelpers module ----------------------------------------------------- diff --git a/docs/geos_posp_docs/PVplugins.rst b/docs/geos_posp_docs/PVplugins.rst index 293e0a65d..fa8fae240 100644 --- a/docs/geos_posp_docs/PVplugins.rst +++ b/docs/geos_posp_docs/PVplugins.rst @@ -39,13 +39,6 @@ PVExtractMergeBlocksVolumeWell plugin .. automodule:: PVplugins.PVExtractMergeBlocksVolumeWell - -PVGeomechanicsAnalysis plugin ---------------------------------------- - -.. automodule:: PVplugins.PVGeomechanicsAnalysis - - PVGeomechanicsWorkflowVolume plugin --------------------------------------------- diff --git a/docs/geos_posp_docs/filters.rst b/docs/geos_posp_docs/filters.rst index da4b0e559..5ca2063a8 100644 --- a/docs/geos_posp_docs/filters.rst +++ b/docs/geos_posp_docs/filters.rst @@ -19,14 +19,6 @@ geos_posp.filters.AttributeMappingFromCellId module :undoc-members: :show-inheritance: -geos_posp.filters.GeomechanicsCalculator module ---------------------------------------------------- - -.. automodule:: geos_posp.filters.GeomechanicsCalculator - :members: - :undoc-members: - :show-inheritance: - geos_posp.filters.GeosBlockExtractor module ----------------------------------------------- diff --git a/docs/geos_pv_docs/processing.rst b/docs/geos_pv_docs/processing.rst index f5fcaf243..3b4b624a9 100644 --- a/docs/geos_pv_docs/processing.rst +++ b/docs/geos_pv_docs/processing.rst @@ -14,6 +14,12 @@ PVFillPartialArrays .. automodule:: geos.pv.plugins.PVFillPartialArrays +PVGeomechanicsCalculator plugin +--------------------------------------- + +.. automodule:: geos.pv.plugins.PVGeomechanicsCalculator + + PVSplitMesh ---------------------------------- From dc1934ba7fbd06f1c86c17b22b911a5aa902e991 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 23 Sep 2025 12:03:49 +0200 Subject: [PATCH 18/40] Refactor the paraview plugin --- .../pv/plugins/PVGeomechanicsCalculator.py | 311 +++++++----------- 1 file changed, 115 insertions(+), 196 deletions(-) diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py index d1e44c268..27cd61206 100644 --- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py +++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py @@ -1,55 +1,67 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. -# SPDX-FileContributor: Martin Lemay +# SPDX-FileContributor: Martin Lemay, Romain Baville # ruff: noqa: E402 # disable Module level import not at top of file -import os import sys +from pathlib import Path from typing import Union +from typing_extensions import Self +import logging import numpy as np + from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, -) -from typing_extensions import Self +) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/util/vtkAlgorithm.py +from paraview.detail.loghandler import ( # type: ignore[import-not-found] + VTKHandler, +) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py + from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector from vtkmodules.vtkCommonDataModel import vtkPointSet, vtkUnstructuredGrid -dir_path = os.path.dirname( os.path.realpath( __file__ ) ) -parent_dir_path = os.path.dirname( dir_path ) -if parent_dir_path not in sys.path: - sys.path.append( parent_dir_path ) +# update sys.path to load all GEOS Python Package dependencies +geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent +sys.path.insert( 0, str( geos_pv_path / "src" ) ) +from geos.pv.utils.config import update_paths -import PVplugins # noqa: F401 +update_paths() -from geos.utils.Logger import Logger, getLogger +from geos.utils.Logger import ( Logger, getLogger, ) from geos.utils.PhysicalConstants import ( - DEFAULT_FRICTION_ANGLE_DEG, + DEFAULT_FRICTION_ANGLE_RAD, DEFAULT_GRAIN_BULK_MODULUS, DEFAULT_ROCK_COHESION, WATER_DENSITY, ) -from geos_posp.filters.GeomechanicsCalculator import GeomechanicsCalculator +from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator __doc__ = """ -PVGeomechanicsAnalysis is a Paraview plugin that allows to compute -additional geomechanical attributes from the input mesh. +PVGeomechanicsCalculator is a paraview plugin that allows to compute additional +Geomechanical properties from existing ones. + +PVGeomechanicsCalculator paraview plugin input mesh is either vtkPointSet or vtkUnstructuredGrid +and returned mesh is of same type as input. -Input and output types are vtkMultiBlockDataSet. +.. Note:: + To deals with Geos output, you may first process it with PVExtractMergeBlocksVolume To use it: -* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVGeomechanicsAnalysis. -* Select any pipeline child of the first ouput from PVExtractMergeBlocksVolume* filter. -* Search and Apply PVGeomechanicsAnalysis Filter. +* Load the module in Paraview: Tools > Manage Plugins... > Load new > PVGeomechanicsCalculator +* Select the mesh you want to compute geomechanics output on +* Search Filters > 3- Geos Geomechanics > Geos Geomechanics Calculator +* Set physical constants and computeAdvancedOutput if needed +* Apply """ -@smproxy.filter( name="PVGeomechanicsAnalysis", label="Geos Geomechanics Analysis" ) +@smproxy.filter( name="PVGeomechanicsCalculator", label="Geos Geomechanics Calculator" ) @smhint.xml( """""" ) @smproperty.input( name="Input", port_index=0 ) @smdomain.datatype( dataTypes=[ "vtkUnstructuredGrid", "vtkPointSet" ], composite_data_supported=True ) -class PVGeomechanicsAnalysis( VTKPythonAlgorithmBase ): +class PVGeomechanicsCalculator( VTKPythonAlgorithmBase ): def __init__( self: Self ) -> None: """Paraview plugin to compute additional geomechanical outputs. @@ -59,23 +71,16 @@ def __init__( self: Self ) -> None: """ super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkPointSet" ) - # outputs and additional parameters - self.m_computeAdvancedOutputs: bool = False - self.m_grainBulkModulus: float = DEFAULT_GRAIN_BULK_MODULUS - self.m_specificDensity: float = WATER_DENSITY - self.m_rockCohesion: float = DEFAULT_ROCK_COHESION - self.m_frictionAngle: float = DEFAULT_FRICTION_ANGLE_DEG - - # set m_logger - self.m_logger: Logger = getLogger( "Geomechanics Analysis Filter" ) + self.computeAdvancedOutputs: bool = False - def SetLogger( self: Self, logger: Logger ) -> None: - """Set filter logger. + # Defaults physical constants + ## Basic outputs + self.grainBulkModulus: float = DEFAULT_GRAIN_BULK_MODULUS + self.specificDensity: float = WATER_DENSITY + ## Advanced outputs + self.rockCohesion: float = DEFAULT_ROCK_COHESION + self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD - Args: - logger (Logger): logger - """ - self.m_logger = logger @smproperty.doublevector( name="GrainBulkModulus", @@ -89,22 +94,15 @@ def SetLogger( self: Self, logger: Logger ) -> None: The unit is Pa. Default is Quartz bulk modulus (i.e., 38GPa). """ ) - def b01SetGrainBulkModulus( self: Self, value: float ) -> None: + def setGrainBulkModulus( self: Self, grainBulkModulus: float ) -> None: """Set grain bulk modulus. Args: - value (float): grain bulk modulus (Pa) + grainBulkModulus (float): Grain bulk modulus (Pa). """ - self.m_grainBulkModulus = value + self.grainBulkModulus = grainBulkModulus self.Modified() - def getGrainBulkModulus( self: Self ) -> float: - """Access to the grain bulk modulus value. - - Returns: - float: self.m_grainBulkModulus. - """ - return self.m_grainBulkModulus @smproperty.doublevector( name="SpecificDensity", @@ -118,22 +116,15 @@ def getGrainBulkModulus( self: Self ) -> float: The unit is kg/m3. Default is fresh water density (i.e., 1000 kg/m3). """ ) - def b02SetSpecificDensity( self: Self, value: float ) -> None: + def setSpecificDensity( self: Self, specificDensity: float ) -> None: """Set specific density. Args: - value (float): Reference specific density (kg/m3) + specificDensity (float): Reference specific density (kg/m3). """ - self.m_specificDensity = value + self.specificDensity = specificDensity self.Modified() - def getSpecificDensity( self: Self ) -> float: - """Access the specific density value. - - Returns: - float: self.m_specificDensity. - """ - return self.m_specificDensity @smproperty.xml( """ @@ -141,49 +132,33 @@ def getSpecificDensity( self: Self ) -> float: """ ) - def b09GroupBasicOutputParameters( self: Self ) -> None: + def groupBasicOutputParameters( self: Self ) -> None: """Organize groups.""" self.Modified() + @smproperty.intvector( - name="AdvancedOutputsUse", + name="ComputeAdvancedOutputs", label="Compute advanced geomechanical outputs", default_values=0, panel_visibility="default", ) @smdomain.xml( """ - - - Check to compute advanced geomechanical outputs including - reservoir stress paths and fracture indexes. - - """ ) - def c01SetAdvancedOutputs( self: Self, boolean: bool ) -> None: + + + Check to compute advanced geomechanical outputs including + reservoir stress paths and fracture indexes. + + """ ) + def setComputeAdvancedOutputs( self: Self, computeAdvancedOutputs: bool ) -> None: """Set advanced output calculation option. Args: - boolean (bool): if True advanced outputs are computed. + computeAdvancedOutputs (bool): True to compute advanced geomechanical parameters, False otherwise. """ - self.m_computeAdvancedOutputs = boolean + self.computeAdvancedOutputs = computeAdvancedOutputs self.Modified() - def getComputeAdvancedOutputs( self: Self ) -> float: - """Access the advanced outputs option. - - Returns: - float: self.m_computeAdvancedOutputs. - """ - return self.m_computeAdvancedOutputs - - @smproperty.xml( """ - - panel_visibility="default"> - - """ ) - def c09GroupAdvancedOutputsUse( self: Self ) -> None: - """Organize groups.""" - self.Modified() @smproperty.doublevector( name="RockCohesion", @@ -192,93 +167,59 @@ def c09GroupAdvancedOutputsUse( self: Self ) -> None: panel_visibility="default", ) @smdomain.xml( """ - - Reference rock cohesion to compute critical pore pressure. - The unit is Pa.Default is fractured case (i.e., 0. Pa). - - """ ) - def d01SetRockCohesion( self: Self, value: float ) -> None: + + Reference rock cohesion to compute critical pore pressure. + The unit is Pa.Default is fractured case (i.e., 0. Pa). + + """ ) + def setRockCohesion( self: Self, rockCohesion: float ) -> None: """Set rock cohesion. Args: - value (float): rock cohesion (Pa) + rockCohesion (float): Rock cohesion (Pa). """ - self.m_rockCohesion = value + self.rockCohesion = rockCohesion self.Modified() - def getRockCohesion( self: Self ) -> float: - """Get rock cohesion. - - Returns: - float: rock cohesion. - """ - return self.m_rockCohesion @smproperty.doublevector( name="FrictionAngle", - label="Friction Angle (°)", - default_values=DEFAULT_FRICTION_ANGLE_DEG, + label="Friction Angle (rad)", + default_values=DEFAULT_FRICTION_ANGLE_RAD, panel_visibility="default", ) @smdomain.xml( """ - - Reference friction angle to compute critical pore pressure. - The unit is °. Default is no friction case (i.e., 0.°). - - """ ) - def d02SetFrictionAngle( self: Self, value: float ) -> None: - """Set frition angle. + + Reference friction angle to compute critical pore pressure. + The unit is rad. Default is 10.0 / 180.0 * np.pi rad. + + """ ) + def setFrictionAngle( self: Self, frictionAngle: float ) -> None: + """Set friction angle. Args: - value (float): friction angle (°) + frictionAngle (float): Friction angle (rad). """ - self.m_frictionAngle = value + self.frictionAngle = frictionAngle self.Modified() - def getFrictionAngle( self: Self ) -> float: - """Get friction angle in radian. - - Returns: - float: friction angle. - """ - return self.m_frictionAngle * np.pi / 180.0 @smproperty.xml( """ - - - - - - - - """ ) - def d09GroupAdvancedOutputParameters( self: Self ) -> None: + + + + + + + + """ ) + def groupAdvancedOutputParameters( self: Self ) -> None: """Organize groups.""" self.Modified() - def RequestInformation( - self: Self, - request: vtkInformation, # noqa: F841 - inInfoVec: list[ vtkInformationVector ], # noqa: F841 - outInfoVec: vtkInformationVector, - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestInformation. - - Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects - - Returns: - int: 1 if calculation successfully ended, 0 otherwise. - """ - executive = self.GetExecutive() # noqa: F841 - outInfo = outInfoVec.GetInformationObject( 0 ) # noqa: F841 - return 1 def RequestDataObject( self: Self, @@ -289,9 +230,9 @@ def RequestDataObject( """Inherited from VTKPythonAlgorithmBase::RequestDataObject. Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects + request (vtkInformation): Request. + inInfoVec (list[vtkInformationVector]): Input objects. + outInfoVec (vtkInformationVector): Output objects. Returns: int: 1 if calculation successfully ended, 0 otherwise. @@ -304,6 +245,7 @@ def RequestDataObject( outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] + def RequestData( self: Self, request: vtkInformation, # noqa: F841 @@ -313,53 +255,30 @@ def RequestData( """Inherited from VTKPythonAlgorithmBase::RequestData. Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects + request (vtkInformation): Request. + inInfoVec (list[vtkInformationVector]): Input objects. + outInfoVec (vtkInformationVector): Output objects. Returns: int: 1 if calculation successfully ended, 0 otherwise. """ - try: - self.m_logger.info( f"Apply filter {__name__}" ) - - inData = self.GetInputData( inInfoVec, 0, 0 ) - assert inData is not None - - input: Union[ vtkPointSet, vtkUnstructuredGrid ] - output: Union[ vtkPointSet, vtkUnstructuredGrid ] - if inData.IsA( "vtkUnstructuredGrid" ): - input = vtkUnstructuredGrid.GetData( inInfoVec[ 0 ] ) - output = vtkUnstructuredGrid.GetData( outInfoVec ) - elif inData.IsA( "vtkPointSet" ): - input = vtkPointSet.GetData( inInfoVec[ 0 ] ) - output = vtkPointSet.GetData( outInfoVec ) - else: - raise TypeError( "Error type" ) - - assert input is not None, "Input object is null" - assert output is not None, "Output object is null" - - # create new properties - geomechanicsCalculatorFilter: GeomechanicsCalculator = ( GeomechanicsCalculator() ) - geomechanicsCalculatorFilter.SetLogger( self.m_logger ) - geomechanicsCalculatorFilter.SetInputDataObject( input ) - if self.m_computeAdvancedOutputs: - geomechanicsCalculatorFilter.computeAdvancedOutputsOn() - else: - geomechanicsCalculatorFilter.computeAdvancedOutputsOff() - geomechanicsCalculatorFilter.SetGrainBulkModulus( self.getGrainBulkModulus() ) - geomechanicsCalculatorFilter.SetSpecificDensity( self.getSpecificDensity() ) - geomechanicsCalculatorFilter.SetRockCohesion( self.getRockCohesion() ) - geomechanicsCalculatorFilter.SetFrictionAngle( self.getFrictionAngle() ) - geomechanicsCalculatorFilter.Update() - output.ShallowCopy( geomechanicsCalculatorFilter.GetOutputDataObject( 0 ) ) - output.Modified() - - except AssertionError as e: - self.m_logger.error( f"{__name__} filter execution failed due to:" ) - self.m_logger.error( e ) - except Exception as e: - self.m_logger.critical( f"{__name__} filter execution failed due to:" ) - self.m_logger.critical( e, exc_info=True ) + inputMesh: Union[ vtkPointSet, vtkUnstructuredGrid ] = self.GetInputData( inInfoVec, 0, 0 ) + outputMesh: Union[ vtkPointSet, vtkUnstructuredGrid ] = self.GetOutputData( outInfoVec, 0 ) + assert inputMesh is not None, "Input server mesh is null." + assert outputMesh is not None, "Output pipeline is null." + + filter: GeomechanicsCalculator = GeomechanicsCalculator( inputMesh, self.computeAdvancedOutputs, True ) + + if not filter.logger.hasHandlers(): + filter.setLoggerHandler( VTKHandler() ) + + filter.setGrainBulkModulus( self.grainBulkModulus ) + filter.setSpecificDensity( self.specificDensity ) + filter.setRockCohesion( self.rockCohesion ) + filter.setFrictionAngle( self.frictionAngle ) + + if filter.applyFilter(): + outputMesh.ShallowCopy( filter.getOutput() ) + outputMesh.Modified() + return 1 From 96e888090ae0e379f18163da1b7f83c32fd00a4a Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 23 Sep 2025 13:29:58 +0200 Subject: [PATCH 19/40] clean for ci --- .../geos/pv/plugins/PVGeomechanicsCalculator.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py index 27cd61206..bef82ae73 100644 --- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py +++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py @@ -7,15 +7,12 @@ from typing import Union from typing_extensions import Self -import logging -import numpy as np - from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, -) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/util/vtkAlgorithm.py +) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/util/vtkAlgorithm.py from paraview.detail.loghandler import ( # type: ignore[import-not-found] VTKHandler, -) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py +) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector from vtkmodules.vtkCommonDataModel import vtkPointSet, vtkUnstructuredGrid @@ -27,7 +24,6 @@ update_paths() -from geos.utils.Logger import ( Logger, getLogger, ) from geos.utils.PhysicalConstants import ( DEFAULT_FRICTION_ANGLE_RAD, DEFAULT_GRAIN_BULK_MODULUS, @@ -81,7 +77,6 @@ def __init__( self: Self ) -> None: self.rockCohesion: float = DEFAULT_ROCK_COHESION self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD - @smproperty.doublevector( name="GrainBulkModulus", label="Grain bulk modulus (Pa)", @@ -103,7 +98,6 @@ def setGrainBulkModulus( self: Self, grainBulkModulus: float ) -> None: self.grainBulkModulus = grainBulkModulus self.Modified() - @smproperty.doublevector( name="SpecificDensity", label="Specific Density (kg/m3)", @@ -125,7 +119,6 @@ def setSpecificDensity( self: Self, specificDensity: float ) -> None: self.specificDensity = specificDensity self.Modified() - @smproperty.xml( """ @@ -136,7 +129,6 @@ def groupBasicOutputParameters( self: Self ) -> None: """Organize groups.""" self.Modified() - @smproperty.intvector( name="ComputeAdvancedOutputs", label="Compute advanced geomechanical outputs", @@ -159,7 +151,6 @@ def setComputeAdvancedOutputs( self: Self, computeAdvancedOutputs: bool ) -> Non self.computeAdvancedOutputs = computeAdvancedOutputs self.Modified() - @smproperty.doublevector( name="RockCohesion", label="Rock Cohesion (Pa)", @@ -181,7 +172,6 @@ def setRockCohesion( self: Self, rockCohesion: float ) -> None: self.rockCohesion = rockCohesion self.Modified() - @smproperty.doublevector( name="FrictionAngle", label="Friction Angle (rad)", @@ -203,7 +193,6 @@ def setFrictionAngle( self: Self, frictionAngle: float ) -> None: self.frictionAngle = frictionAngle self.Modified() - @smproperty.xml( """ @@ -220,7 +209,6 @@ def groupAdvancedOutputParameters( self: Self ) -> None: """Organize groups.""" self.Modified() - def RequestDataObject( self: Self, request: vtkInformation, @@ -245,7 +233,6 @@ def RequestDataObject( outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] - def RequestData( self: Self, request: vtkInformation, # noqa: F841 From 4c9c5ce50cf52eac7837f1a26ffa8fa8e4a30f64 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Wed, 24 Sep 2025 17:40:38 +0200 Subject: [PATCH 20/40] clean the doc --- .../geomechanicsCalculatorFunctions.py | 3 +- .../mesh/processing/GeomechanicsCalculator.py | 264 +++++++++--------- .../pv/plugins/PVGeomechanicsCalculator.py | 17 +- 3 files changed, 154 insertions(+), 130 deletions(-) diff --git a/geos-geomechanics/src/geos/geomechanics/processing/geomechanicsCalculatorFunctions.py b/geos-geomechanics/src/geos/geomechanics/processing/geomechanicsCalculatorFunctions.py index 44aa940bf..4c7b16e7c 100644 --- a/geos-geomechanics/src/geos/geomechanics/processing/geomechanicsCalculatorFunctions.py +++ b/geos-geomechanics/src/geos/geomechanics/processing/geomechanicsCalculatorFunctions.py @@ -506,8 +506,7 @@ def reservoirStressPathReal( deltaStress: npt.NDArray[ np.float64 ], den: npt.NDArray[ np.float64 ] = np.copy( deltaPressure ) den[ mask ] = 1.0 # use -1 to agree with Geos convention (i.e., compression with negative stress) - # take the xx, yy, and zz components only - rsp: npt.NDArray[ np.float64 ] = np.copy( deltaStress[ :, :3 ] ) + rsp: npt.NDArray[ np.float64 ] = np.copy( deltaStress[ :, : ] ) for j in range( rsp.shape[ 1 ] ): rsp[ :, j ] /= den rsp[ mask, j ] = np.nan diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index 647bd4eee..d70d52816 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -14,7 +14,6 @@ from geos.mesh.utils.arrayModifiers import createAttribute from geos.mesh.utils.arrayHelpers import ( getArrayInObject, - getComponentNames, isAttributeInObject, ) @@ -24,6 +23,7 @@ ) from geos.utils.GeosOutputsConstants import ( AttributeEnum, + ComponentNameEnum, GeosMeshOutputsEnum, PostProcessingOutputsEnum, ) @@ -42,6 +42,21 @@ GeomechanicsCalculator module is a vtk filter that allows to compute additional Geomechanical properties from existing ones. +The basic geomechanics outputs are: + - Elastic modulus (young modulus and poisson ratio or bulk modulus and shear modulus) + - Biot coefficient + - Compressibility, oedometric compressibility and real compressibility coefficient + - Specific gravity + - Real effective stress ratio + - Total initial stress, total current stress and total stress ratio + - Lithostatic stress (physic to update) + - Elastic stain + - Reservoir stress path and reservoir stress path in oedometric condition + +The advanced geomechanics outputs are: + - fracture index and threshold + - Critical pore pressure and pressure index + GeomechanicsCalculator filter input mesh is either vtkPointSet or vtkUnstructuredGrid and returned mesh is of same type as input. @@ -72,13 +87,15 @@ filter.setLoggerHandler( yourHandler ) # Change the physical constants if needed + ## Basic outputs grainBulkModulus: float specificDensity: float - rockCohesion: float - frictionAngle: float - filter.SetGrainBulkModulus(grainBulkModulus) filter.SetSpecificDensity(specificDensity) + + ## Advanced outputs + rockCohesion: float + frictionAngle: float filter.SetRockCohesion(rockCohesion) filter.SetFrictionAngle(frictionAngle) @@ -120,22 +137,20 @@ def __init__( self.doComputeAdvancedOutputs: bool = computeAdvancedOutputs # Defaults physical constants + ## Basic outputs self.grainBulkModulus: float = DEFAULT_GRAIN_BULK_MODULUS self.specificDensity: float = WATER_DENSITY + ## Advanced outputs self.rockCohesion: float = DEFAULT_ROCK_COHESION self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD - # Elastic moduli are either bulk and Shear moduli (computeYoungPoisson=True) - # or young Modulus and poisson's ratio (computeYoungPoisson=False) - self.computeYoungPoisson: bool = True - # Logger. - self.m_logger: Logger + self.logger: Logger if not speHandler: - self.m_logger = getLogger( loggerTitle, True ) + self.logger = getLogger( loggerTitle, True ) else: - self.m_logger = logging.getLogger( loggerTitle ) - self.m_logger.setLevel( logging.INFO ) + self.logger = logging.getLogger( loggerTitle ) + self.logger.setLevel( logging.INFO ) def applyFilter( self: Self ) -> bool: """Compute the geomechanical outputs of the mesh. @@ -144,7 +159,7 @@ def applyFilter( self: Self ) -> bool: Boolean (bool): True if calculation successfully ended, False otherwise. """ if not self.checkMandatoryAttributes(): - self.m_logger.error( "The filter failed." ) + self.logger.error( "The filter failed." ) return False if not self.computeBasicOutputs(): @@ -171,10 +186,10 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: Args: handler (logging.Handler): The handler to add. """ - if not self.m_logger.hasHandlers(): - self.m_logger.addHandler( handler ) + if not self.logger.hasHandlers(): + self.logger.addHandler( handler ) else: - self.m_logger.warning( + self.logger.warning( "The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization." ) @@ -229,33 +244,40 @@ def checkMandatoryAttributes( self: Self ) -> bool: bool: True if all needed attributes are present, False otherwise """ self.youngModulusAttributeName: str = PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName - self.poissonRatioAttributeName: str = PostProcessingOutputsEnum.POISSON_RATIO.attributeName self.youngModulusOnPoints: bool = PostProcessingOutputsEnum.YOUNG_MODULUS.isOnPoints + youngModulusOnMesh: bool = isAttributeInObject( self.output, self.youngModulusAttributeName, self.youngModulusOnPoints ) + + self.poissonRatioAttributeName: str = PostProcessingOutputsEnum.POISSON_RATIO.attributeName self.poissonRatioOnPoints: bool = PostProcessingOutputsEnum.POISSON_RATIO.isOnPoints + poissonRationOnMesh: bool = isAttributeInObject( self.output, self.poissonRatioAttributeName, self.poissonRatioOnPoints ) - self.computeYoungPoisson = not isAttributeInObject( self.output, self.youngModulusAttributeName, self.youngModulusOnPoints ) \ - or not isAttributeInObject( self.output, self.poissonRatioAttributeName, self.poissonRatioOnPoints ) + self.bulkModulusAttributeName: str = GeosMeshOutputsEnum.BULK_MODULUS.attributeName + self.bulkModulusOnPoints: bool = GeosMeshOutputsEnum.BULK_MODULUS.isOnPoints + bulkModulusOnMesh: bool = isAttributeInObject( self.output, self.bulkModulusAttributeName, self.bulkModulusOnPoints ) - # if none of elastic moduli is present, return False - if self.computeYoungPoisson: - self.bulkModulusAttributeName: str = GeosMeshOutputsEnum.BULK_MODULUS.attributeName - self.shearModulusAttributeName: str = GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName - self.bulkModulusOnPoints: str = GeosMeshOutputsEnum.BULK_MODULUS.isOnPoints - self.shearModulusOnPoints: str = GeosMeshOutputsEnum.SHEAR_MODULUS.isOnPoints - - if not isAttributeInObject( self.output, self.bulkModulusAttributeName, self.bulkModulusOnPoints ) \ - or not isAttributeInObject( self.output, self.shearModulusAttributeName, self.shearModulusOnPoints ): - mess: str = ( "Mandatory properties are missing to compute geomechanical outputs:" ) - mess += ( - f"Either { self.bulkModulusAttributeName } and { self.shearModulusAttributeName } or { self.youngModulusAttributeName } and { self.poissonRatioAttributeName } must be present in the table data." - ) - self.m_logger.error( mess ) + self.shearModulusAttributeName: str = GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName + self.shearModulusOnPoints: bool = GeosMeshOutputsEnum.SHEAR_MODULUS.isOnPoints + shearModulusOnMesh: bool = isAttributeInObject( self.output, self.shearModulusAttributeName, self.shearModulusOnPoints ) + + if not youngModulusOnMesh and not poissonRationOnMesh: + if bulkModulusOnMesh and shearModulusOnMesh: + self.computeYoungPoisson = True + else: + self.logger.error( f"{ self.bulkModulusAttributeName } or { self.shearModulusAttributeName } are missing to compute geomechanical outputs." ) return False + elif not bulkModulusOnMesh and not shearModulusOnMesh: + if youngModulusOnMesh and poissonRationOnMesh: + self.computeYoungPoisson = False + else: + self.logger.error( f"{ self.youngModulusAttributeName } or { self.poissonRatioAttributeName } are missing to compute geomechanical outputs." ) + else: + self.logger.error( f"{ self.bulkModulusAttributeName } and { self.shearModulusAttributeName } or { self.youngModulusAttributeName } and { self.poissonRatioAttributeName } are mandatory to compute geomechanical outputs." ) + return False porosityAttributeName: str = GeosMeshOutputsEnum.POROSITY.attributeName porosityOnPoints: bool = GeosMeshOutputsEnum.POROSITY.isOnPoints if not isAttributeInObject( self.output, porosityAttributeName, porosityOnPoints ): - self.m_logger.error( + self.logger.error( f"The mandatory attribute { porosityAttributeName } is missing to compute geomechanical outputs." ) return False else: @@ -265,7 +287,7 @@ def checkMandatoryAttributes( self: Self ) -> bool: porosityInitialAttributeName: str = GeosMeshOutputsEnum.POROSITY_INI.attributeName porosityInitialOnPoints: bool = GeosMeshOutputsEnum.POROSITY_INI.isOnPoints if not isAttributeInObject( self.output, porosityInitialAttributeName, porosityInitialOnPoints ): - self.m_logger.error( + self.logger.error( f"The mandatory attribute { porosityInitialAttributeName } is missing to compute geomechanical outputs." ) return False @@ -277,7 +299,7 @@ def checkMandatoryAttributes( self: Self ) -> bool: deltaPressureAttributeName: str = GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName deltaPressureOnPoint: bool = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints if not isAttributeInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ): - self.m_logger.error( + self.logger.error( f"The mandatory attribute { deltaPressureAttributeName } is missing to compute geomechanical outputs." ) return False else: @@ -287,7 +309,7 @@ def checkMandatoryAttributes( self: Self ) -> bool: densityAttributeName: str = GeosMeshOutputsEnum.ROCK_DENSITY.attributeName densityOnPoints: bool = GeosMeshOutputsEnum.ROCK_DENSITY.isOnPoints if not isAttributeInObject( self.output, densityAttributeName, densityOnPoints ): - self.m_logger.error( + self.logger.error( f"The mandatory attribute { densityAttributeName } is missing to compute geomechanical outputs." ) return False else: @@ -297,7 +319,7 @@ def checkMandatoryAttributes( self: Self ) -> bool: effectiveStressAttributeName: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName effectiveStressOnPoints: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.isOnPoints if not isAttributeInObject( self.output, effectiveStressAttributeName, effectiveStressOnPoints ): - self.m_logger.error( + self.logger.error( f"The mandatory attribute { effectiveStressAttributeName } is missing to compute geomechanical outputs." ) return False @@ -309,7 +331,7 @@ def checkMandatoryAttributes( self: Self ) -> bool: effectiveStressT0AttributeName: str = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.attributeName effectiveStressT0OnPoints: bool = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.isOnPoints if not isAttributeInObject( self.output, effectiveStressT0AttributeName, effectiveStressT0OnPoints ): - self.m_logger.error( + self.logger.error( f"The mandatory attribute { effectiveStressT0AttributeName } is missing to compute geomechanical outputs." ) return False @@ -321,7 +343,7 @@ def checkMandatoryAttributes( self: Self ) -> bool: pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName pressureOnPoints: bool = GeosMeshOutputsEnum.PRESSURE.isOnPoints if not isAttributeInObject( self.output, pressureAttributeName, pressureOnPoints ): - self.m_logger.error( + self.logger.error( f"The mandatory attribute { pressureAttributeName } is missing to compute geomechanical outputs." ) return False else: @@ -337,49 +359,49 @@ def computeBasicOutputs( self: Self ) -> bool: bool: return True if calculation successfully ended, False otherwise. """ if not self.computeElasticModulus(): - self.m_logger.error( "Elastic modulus computation failed." ) + self.logger.error( "Elastic modulus computation failed." ) return False if not self.computeBiotCoefficient(): - self.m_logger.error( "Biot coefficient computation failed." ) + self.logger.error( "Biot coefficient computation failed." ) return False if not self.computeCompressibilityCoefficient(): return False if not self.computeRealEffectiveStressRatio(): - self.m_logger.error( "Effective stress ratio computation failed." ) + self.logger.error( "Effective stress ratio computation failed." ) return False if not self.computeSpecificGravity(): - self.m_logger.error( "Specific gravity computation failed." ) + self.logger.error( "Specific gravity computation failed." ) return False # TODO: deactivate lithostatic stress calculation until right formula # if not self.computeLithostaticStress(): - # self.m_logger.error( "Lithostatic stress computation failed." ) + # self.logger.error( "Lithostatic stress computation failed." ) # return False if not self.computeTotalStresses(): return False if not self.computeElasticStrain(): - self.m_logger.error( "Elastic strain computation failed." ) + self.logger.error( "Elastic strain computation failed." ) return False # oedometric DRSP (effective stress ratio in oedometric conditions) if not self.computeEffectiveStressRatioOed(): - self.m_logger.error( "Effective stress ration in oedometric condition computation failed." ) + self.logger.error( "Effective stress ration in oedometric condition computation failed." ) return False if not self.computeReservoirStressPathOed(): - self.m_logger.error( "Reservoir stress path in oedometric condition computation failed." ) + self.logger.error( "Reservoir stress path in oedometric condition computation failed." ) return False if not self.computeReservoirStressPathReal(): return False - self.m_logger.info( "All geomechanical basic outputs were successfully computed." ) + self.logger.info( "All geomechanical basic outputs were successfully computed." ) return True def computeAdvancedOutputs( self: Self ) -> bool: @@ -395,7 +417,7 @@ def computeAdvancedOutputs( self: Self ) -> bool: return False mess: str = ( "All geomechanical advanced outputs were successfully computed." ) - self.m_logger.info( mess ) + self.logger.info( mess ) return True def computeElasticModulus( self: Self ) -> bool: @@ -427,28 +449,28 @@ def computeElasticModulusFromBulkShear( self: Self ) -> bool: self.youngModulus = fcts.youngModulus( self.bulkModulus, self.shearModulus ) if np.any( self.youngModulus < 0 ): - self.m_logger.error( "Young modulus yields negative values. Check Bulk and Shear modulus values." ) + self.logger.error( "Young modulus yields negative values. Check Bulk and Shear modulus values." ) return False if not createAttribute( self.output, self.youngModulus, self.youngModulusAttributeName, onPoints=self.youngModulusOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Young modulus computation failed." ) + logger=self.logger ): + self.logger.error( "Young modulus computation failed." ) return False self.poissonRatio = fcts.poissonRatio( self.bulkModulus, self.shearModulus ) if np.any( self.poissonRatio < 0 ): - self.m_logger.error( "Poisson ratio yields negative values. Check Bulk and Shear modulus values." ) + self.logger.error( "Poisson ratio yields negative values. Check Bulk and Shear modulus values." ) return False if not createAttribute( self.output, self.poissonRatio, self.poissonRatioAttributeName, onPoints=self.poissonRatioOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Poisson ration computation failed." ) + logger=self.logger ): + self.logger.error( "Poisson ration computation failed." ) return False return True @@ -464,28 +486,28 @@ def computeElasticModulusFromYoungPoisson( self: Self ) -> bool: self.bulkModulus = fcts.bulkModulus( self.youngModulus, self.poissonRatio ) if np.any( self.bulkModulus < 0 ): - self.m_logger.error( "Bulk modulus yields negative values. Check Young modulus and Poisson ratio values." ) + self.logger.error( "Bulk modulus yields negative values. Check Young modulus and Poisson ratio values." ) return False if not createAttribute( self.output, self.bulkModulus, self.bulkModulusAttributeName, onPoints=self.bulkModulusOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Bulk modulus computation failed." ) + logger=self.logger ): + self.logger.error( "Bulk modulus computation failed." ) return False self.shearModulus = fcts.shearModulus( self.youngModulus, self.poissonRatio ) if np.any( self.shearModulus < 0 ): - self.m_logger.error( "Shear modulus yields negative values. Check Young modulus and Poisson ratio values." ) + self.logger.error( "Shear modulus yields negative values. Check Young modulus and Poisson ratio values." ) return False if not createAttribute( self.output, self.shearModulus, self.shearModulusAttributeName, onPoints=self.shearModulusOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Shear modulus computation failed." ) + logger=self.logger ): + self.logger.error( "Shear modulus computation failed." ) return False return True @@ -498,13 +520,19 @@ def computeBiotCoefficient( self: Self ) -> bool: """ biotCoefficientAttributeName: str = PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName biotCoefficientOnPoints: bool = PostProcessingOutputsEnum.BIOT_COEFFICIENT.isOnPoints - self.biotCoefficient: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.grainBulkModulus, - self.bulkModulus ) - return createAttribute( self.output, - self.biotCoefficient, - biotCoefficientAttributeName, - onPoints=biotCoefficientOnPoints, - logger=self.m_logger ) + self.biotCoefficient: npt.NDArray[ np.float64 ] + + if not isAttributeInObject( self.output, biotCoefficientAttributeName, biotCoefficientOnPoints ): + self.biotCoefficient = fcts.biotCoefficient( self.grainBulkModulus, self.bulkModulus ) + return createAttribute( self.output, + self.biotCoefficient, + biotCoefficientAttributeName, + onPoints=biotCoefficientOnPoints, + logger=self.logger ) + else: + self.biotCoefficient = getArrayInObject( self.output, biotCoefficientAttributeName, biotCoefficientOnPoints ) + self.logger.warning( f"{ biotCoefficientAttributeName } is already on the mesh, it has not been computed by the filter." ) + return True def computeCompressibilityCoefficient( self: Self ) -> bool: """Compute compressibility coefficient from simulation outputs. @@ -523,8 +551,8 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: compressibility, compressibilityAttributeName, onPoints=compressibilityOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Compressibility coefficient computation failed." ) + logger=self.logger ): + self.logger.error( "Compressibility coefficient computation failed." ) return False # oedometric compressibility @@ -536,8 +564,8 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: compressibilityOed, compressibilityOedAttributeName, onPoints=compressibilityOedOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Oedometric compressibility coefficient computation failed." ) + logger=self.logger ): + self.logger.error( "Oedometric compressibility coefficient computation failed." ) return False # real compressibility @@ -549,8 +577,8 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: compressibilityReal, compressibilityRealAttributeName, onPoints=compressibilityRealOnPoint, - logger=self.m_logger ): - self.m_logger.error( "Real compressibility coefficient computation failed." ) + logger=self.logger ): + self.logger.error( "Real compressibility coefficient computation failed." ) return False return True @@ -571,7 +599,7 @@ def computeSpecificGravity( self: Self ) -> bool: specificGravity, specificGravityAttributeName, onPoints=specificGravityOnPoints, - logger=self.m_logger ) + logger=self.logger ) def computeRealEffectiveStressRatio( self: Self ) -> bool: """Compute effective stress ratio. @@ -593,29 +621,26 @@ def computeTotalStresses( self: Self ) -> bool: """ # Compute total stress at initial time step. if not self.computeTotalStressInitial(): - self.m_logger.error( "Total stress at initial time step computation failed." ) + self.logger.error( "Total stress at initial time step computation failed." ) return False # Compute total stress at current time step. totalStressAttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL.attributeName totalStressOnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL.isOnPoints - totalStressComponentNames: Tuple[ str, ...] = () - if PostProcessingOutputsEnum.STRESS_TOTAL.nbComponent > 1: - totalStressComponentNames = getComponentNames( self.output, totalStressAttributeName, totalStressOnPoints ) self.totalStress: npt.NDArray[ np.float64 ] = self.doComputeTotalStress( self.effectiveStress, self.pressure, self.biotCoefficient ) if not createAttribute( self.output, self.totalStress, totalStressAttributeName, - componentNames=totalStressComponentNames, + componentNames=ComponentNameEnum.XYZ.value, onPoints=totalStressOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Total stress at current time step computation failed." ) + logger=self.logger ): + self.logger.error( "Total stress at current time step computation failed." ) return False # Compute total stress ratio. if not self.computeStressRatioReal( self.totalStress, PostProcessingOutputsEnum.STRESS_TOTAL_RATIO_REAL ): - self.m_logger.error( "Total stress ratio computation failed." ) + self.logger.error( "Total stress ratio computation failed." ) return False return True @@ -646,12 +671,13 @@ def computeTotalStressInitial( self: Self ) -> bool: poissonRatioT0OnPoints ) bulkModulusT0 = fcts.bulkModulus( youngModulusT0, poissonRatioT0 ) else: - self.m_logger( "Elastic moduli at initial time are absent." ) + self.logger( "Elastic moduli at initial time are absent." ) + return False # Compute Biot at initial time step. biotCoefficientT0: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.grainBulkModulus, bulkModulusT0 ) - pressureT0: npt.NDArray[ np.float64 ] + pressureT0: Union[ npt.NDArray[ np.float64 ], None ] = None # Case pressureT0 is None, total stress = effective stress # (managed by doComputeTotalStress function) if self.pressure is not None: @@ -660,17 +686,13 @@ def computeTotalStressInitial( self: Self ) -> bool: totalStressT0AttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.attributeName totalStressT0OnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.isOnPoints - totalStressT0ComponentNames: Tuple[ str, ...] = () - if PostProcessingOutputsEnum.STRESS_TOTAL.nbComponent > 1: - totalStressT0ComponentNames = getComponentNames( self.output, totalStressT0AttributeName, - totalStressT0OnPoints ) self.totalStressT0 = self.doComputeTotalStress( self.effectiveStressT0, pressureT0, biotCoefficientT0 ) return createAttribute( self.output, self.totalStressT0, totalStressT0AttributeName, - componentNames=totalStressT0ComponentNames, + componentNames=ComponentNameEnum.XYZ.value, onPoints=totalStressT0OnPoints, - logger=self.m_logger ) + logger=self.logger ) def doComputeTotalStress( self: Self, @@ -691,10 +713,10 @@ def doComputeTotalStress( totalStress: npt.NDArray[ np.float64 ] if pressure is None: totalStress = np.copy( effectiveStress ) - self.m_logger.warning( "Pressure attribute is undefined, total stress will be equal to effective stress." ) + self.logger.warning( "Pressure attribute is undefined, total stress will be equal to effective stress." ) else: if np.isnan( pressure ).any(): - self.m_logger.warning( "Some cells do not have pressure data, for those cells, pressure is set to 0." ) + self.logger.warning( "Some cells do not have pressure data, for those cells, pressure is set to 0." ) pressure[ np.isnan( pressure ) ] = 0.0 totalStress = fcts.totalStress( effectiveStress, biotCoefficient, pressure ) @@ -717,7 +739,7 @@ def computeLithostaticStress( self: Self ) -> bool: lithostaticStress, lithostaticStressAttributeName, onPoints=lithostaticStressOnPoint, - logger=self.m_logger ) + logger=self.logger ) def computeDepthAlongLine( self: Self ) -> npt.NDArray[ np.float64 ]: """Compute depth along a line. @@ -775,10 +797,6 @@ def computeElasticStrain( self: Self ) -> bool: elasticStrainAttributeName: str = PostProcessingOutputsEnum.STRAIN_ELASTIC.attributeName elasticStrainOnPoints: bool = PostProcessingOutputsEnum.STRAIN_ELASTIC.isOnPoints - elasticStrainComponentNames: Tuple[ str, ...] = () - if PostProcessingOutputsEnum.STRESS_TOTAL.nbComponent > 1: - elasticStrainComponentNames = getComponentNames( self.output, elasticStrainAttributeName, - elasticStrainOnPoints ) elasticStrain: npt.NDArray[ np.float64 ] if self.computeYoungPoisson: elasticStrain = fcts.elasticStrainFromBulkShear( deltaEffectiveStress, self.bulkModulus, self.shearModulus ) @@ -789,9 +807,9 @@ def computeElasticStrain( self: Self ) -> bool: return createAttribute( self.output, elasticStrain, elasticStrainAttributeName, - componentNames=elasticStrainComponentNames, + componentNames=ComponentNameEnum.XYZ.value, onPoints=elasticStrainOnPoints, - logger=self.m_logger ) + logger=self.logger ) def computeReservoirStressPathReal( self: Self ) -> bool: """Compute reservoir stress paths. @@ -802,34 +820,27 @@ def computeReservoirStressPathReal( self: Self ) -> bool: # create delta stress attribute for QC deltaTotalStressAttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.attributeName deltaTotalStressOnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.isOnPoints - deltaTotalStressComponentNames: Tuple[ str, ...] = () - if PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.nbComponent > 1: - deltaTotalStressComponentNames = getComponentNames( self.output, deltaTotalStressAttributeName, - deltaTotalStressOnPoints ) self.deltaTotalStress: npt.NDArray[ np.float64 ] = self.totalStress - self.totalStressT0 if not createAttribute( self.output, self.deltaTotalStress, deltaTotalStressAttributeName, - componentNames=deltaTotalStressComponentNames, + componentNames=ComponentNameEnum.XYZ.value, onPoints=deltaTotalStressOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Delta total stress computation failed." ) + logger=self.logger ): + self.logger.error( "Delta total stress computation failed." ) return False rspRealAttributeName: str = PostProcessingOutputsEnum.RSP_REAL.attributeName rspRealOnPoints: bool = PostProcessingOutputsEnum.RSP_REAL.isOnPoints - rspRealComponentNames: Tuple[ str, ...] = () - if PostProcessingOutputsEnum.RSP_REAL.nbComponent > 1: - rspRealComponentNames = getComponentNames( self.output, rspRealAttributeName, rspRealOnPoints ) self.rspReal: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathReal( self.deltaTotalStress, self.deltaPressure ) if not createAttribute( self.output, self.rspReal, rspRealAttributeName, - componentNames=rspRealComponentNames, + componentNames=ComponentNameEnum.XYZ.value, onPoints=rspRealOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Reservoir stress real path computation failed." ) + logger=self.logger ): + self.logger.error( "Reservoir stress real path computation failed." ) return False return True @@ -847,7 +858,7 @@ def computeReservoirStressPathOed( self: Self ) -> bool: self.rspOed, rspOedAttributeName, onPoints=rspOedOnPoints, - logger=self.m_logger ) + logger=self.logger ) def computeStressRatioReal( self: Self, stress: npt.NDArray[ np.float64 ], outputAttribute: AttributeEnum ) -> bool: """Compute the ratio between horizontal and vertical effective stress. @@ -866,7 +877,7 @@ def computeStressRatioReal( self: Self, stress: npt.NDArray[ np.float64 ], outpu self.stressRatioReal, stressRatioRealAttributeName, onPoints=stressRatioRealOnPoints, - logger=self.m_logger ) + logger=self.logger ) def computeEffectiveStressRatioOed( self: Self ) -> bool: """Compute the effective stress ratio in oedometric conditions. @@ -881,7 +892,7 @@ def computeEffectiveStressRatioOed( self: Self ) -> bool: effectiveStressRatioOed, effectiveStressRatioOedAttributeName, onPoints=effectiveStressRatioOedOnPoints, - logger=self.m_logger ) + logger=self.logger ) def computeCriticalTotalStressRatio( self: Self ) -> bool: """Compute fracture index and fracture threshold. @@ -898,8 +909,8 @@ def computeCriticalTotalStressRatio( self: Self ) -> bool: criticalTotalStressRatio, fractureIndexAttributeName, onPoints=fractureIndexOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Fracture index computation failed." ) + logger=self.logger ): + self.logger.error( "Fracture index computation failed." ) return False mask: npt.NDArray[ np.bool_ ] = np.argmin( np.abs( self.totalStress[ :, :2 ] ), axis=1 ) @@ -914,8 +925,8 @@ def computeCriticalTotalStressRatio( self: Self ) -> bool: stressRatioThreshold, fractureThresholdAttributeName, onPoints=fractureThresholdOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Fracture threshold computation failed." ) + logger=self.logger ): + self.logger.error( "Fracture threshold computation failed." ) return False return True @@ -935,21 +946,20 @@ def computeCriticalPorePressure( self: Self ) -> bool: criticalPorePressure, criticalPorePressureAttributeName, onPoints=criticalPorePressureOnPoints, - logger=self.m_logger ): - self.m_logger.error( "Critical pore pressure computation failed." ) + logger=self.logger ): + self.logger.error( "Critical pore pressure computation failed." ) return False # add critical pore pressure index (i.e., ratio between pressure and criticalPorePressure) - criticalPorePressureIndex: npt.NDArray[ np.float64 ] = ( fcts.criticalPorePressureThreshold( - self.pressure, criticalPorePressure ) ) + criticalPorePressureIndex: npt.NDArray[ np.float64 ] = fcts.criticalPorePressureThreshold( self.pressure, criticalPorePressure ) criticalPorePressureIndexAttributeName: str = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName criticalPorePressureIndexOnPoint: bool = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.isOnPoints if not createAttribute( self.output, criticalPorePressureIndex, criticalPorePressureIndexAttributeName, onPoints=criticalPorePressureIndexOnPoint, - logger=self.m_logger ): - self.m_logger.error( "Critical pore pressure indexes computation failed." ) + logger=self.logger ): + self.logger.error( "Critical pore pressure indexes computation failed." ) return False return True diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py index bef82ae73..63d266d41 100644 --- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py +++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py @@ -36,6 +36,21 @@ PVGeomechanicsCalculator is a paraview plugin that allows to compute additional Geomechanical properties from existing ones. +The basic geomechanics outputs are: + - Elastic modulus (young modulus and poisson ratio or bulk modulus and shear modulus) + - Biot coefficient + - Compressibility, oedometric compressibility and real compressibility coefficient + - Specific gravity + - Real effective stress ratio + - Total initial stress, total current stress and total stress ratio + - Lithostatic stress (physic to update) + - Elastic stain + - Reservoir stress path and reservoir stress path in oedometric condition + +The advanced geomechanics outputs are: + - fracture index and threshold + - Critical pore pressure and pressure index + PVGeomechanicsCalculator paraview plugin input mesh is either vtkPointSet or vtkUnstructuredGrid and returned mesh is of same type as input. @@ -194,7 +209,7 @@ def setFrictionAngle( self: Self, frictionAngle: float ) -> None: self.Modified() @smproperty.xml( """ - + From 62e4b23132c4d557b9a04d2f6278607eb743045b Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Wed, 24 Sep 2025 17:43:37 +0200 Subject: [PATCH 21/40] fix the ci --- .../mesh/processing/GeomechanicsCalculator.py | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py index d70d52816..26fad5c56 100644 --- a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py +++ b/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay, Romain Baville # ruff: noqa: E402 # disable Module level import not at top of file -from typing import Union, Tuple +from typing import Union from typing_extensions import Self import logging @@ -245,33 +245,43 @@ def checkMandatoryAttributes( self: Self ) -> bool: """ self.youngModulusAttributeName: str = PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName self.youngModulusOnPoints: bool = PostProcessingOutputsEnum.YOUNG_MODULUS.isOnPoints - youngModulusOnMesh: bool = isAttributeInObject( self.output, self.youngModulusAttributeName, self.youngModulusOnPoints ) + youngModulusOnMesh: bool = isAttributeInObject( self.output, self.youngModulusAttributeName, + self.youngModulusOnPoints ) self.poissonRatioAttributeName: str = PostProcessingOutputsEnum.POISSON_RATIO.attributeName self.poissonRatioOnPoints: bool = PostProcessingOutputsEnum.POISSON_RATIO.isOnPoints - poissonRationOnMesh: bool = isAttributeInObject( self.output, self.poissonRatioAttributeName, self.poissonRatioOnPoints ) + poissonRationOnMesh: bool = isAttributeInObject( self.output, self.poissonRatioAttributeName, + self.poissonRatioOnPoints ) self.bulkModulusAttributeName: str = GeosMeshOutputsEnum.BULK_MODULUS.attributeName self.bulkModulusOnPoints: bool = GeosMeshOutputsEnum.BULK_MODULUS.isOnPoints - bulkModulusOnMesh: bool = isAttributeInObject( self.output, self.bulkModulusAttributeName, self.bulkModulusOnPoints ) + bulkModulusOnMesh: bool = isAttributeInObject( self.output, self.bulkModulusAttributeName, + self.bulkModulusOnPoints ) self.shearModulusAttributeName: str = GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName self.shearModulusOnPoints: bool = GeosMeshOutputsEnum.SHEAR_MODULUS.isOnPoints - shearModulusOnMesh: bool = isAttributeInObject( self.output, self.shearModulusAttributeName, self.shearModulusOnPoints ) + shearModulusOnMesh: bool = isAttributeInObject( self.output, self.shearModulusAttributeName, + self.shearModulusOnPoints ) if not youngModulusOnMesh and not poissonRationOnMesh: if bulkModulusOnMesh and shearModulusOnMesh: self.computeYoungPoisson = True else: - self.logger.error( f"{ self.bulkModulusAttributeName } or { self.shearModulusAttributeName } are missing to compute geomechanical outputs." ) + self.logger.error( + f"{ self.bulkModulusAttributeName } or { self.shearModulusAttributeName } are missing to compute geomechanical outputs." + ) return False elif not bulkModulusOnMesh and not shearModulusOnMesh: if youngModulusOnMesh and poissonRationOnMesh: self.computeYoungPoisson = False else: - self.logger.error( f"{ self.youngModulusAttributeName } or { self.poissonRatioAttributeName } are missing to compute geomechanical outputs." ) + self.logger.error( + f"{ self.youngModulusAttributeName } or { self.poissonRatioAttributeName } are missing to compute geomechanical outputs." + ) else: - self.logger.error( f"{ self.bulkModulusAttributeName } and { self.shearModulusAttributeName } or { self.youngModulusAttributeName } and { self.poissonRatioAttributeName } are mandatory to compute geomechanical outputs." ) + self.logger.error( + f"{ self.bulkModulusAttributeName } and { self.shearModulusAttributeName } or { self.youngModulusAttributeName } and { self.poissonRatioAttributeName } are mandatory to compute geomechanical outputs." + ) return False porosityAttributeName: str = GeosMeshOutputsEnum.POROSITY.attributeName @@ -530,8 +540,10 @@ def computeBiotCoefficient( self: Self ) -> bool: onPoints=biotCoefficientOnPoints, logger=self.logger ) else: - self.biotCoefficient = getArrayInObject( self.output, biotCoefficientAttributeName, biotCoefficientOnPoints ) - self.logger.warning( f"{ biotCoefficientAttributeName } is already on the mesh, it has not been computed by the filter." ) + self.biotCoefficient = getArrayInObject( self.output, biotCoefficientAttributeName, + biotCoefficientOnPoints ) + self.logger.warning( + f"{ biotCoefficientAttributeName } is already on the mesh, it has not been computed by the filter." ) return True def computeCompressibilityCoefficient( self: Self ) -> bool: @@ -951,7 +963,8 @@ def computeCriticalPorePressure( self: Self ) -> bool: return False # add critical pore pressure index (i.e., ratio between pressure and criticalPorePressure) - criticalPorePressureIndex: npt.NDArray[ np.float64 ] = fcts.criticalPorePressureThreshold( self.pressure, criticalPorePressure ) + criticalPorePressureIndex: npt.NDArray[ np.float64 ] = fcts.criticalPorePressureThreshold( + self.pressure, criticalPorePressure ) criticalPorePressureIndexAttributeName: str = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName criticalPorePressureIndexOnPoint: bool = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.isOnPoints if not createAttribute( self.output, From 9d37f5c5f3d249f27eb46a4b7187f96c0cad41ea Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Wed, 24 Sep 2025 18:00:58 +0200 Subject: [PATCH 22/40] Update the workflow plugins using the filter --- .../PVplugins/PVGeomechanicsWorkflowVolume.py | 36 ++++++++++--------- .../PVGeomechanicsWorkflowVolumeSurface.py | 36 ++++++++++--------- ...PVGeomechanicsWorkflowVolumeSurfaceWell.py | 36 ++++++++++--------- .../PVGeomechanicsWorkflowVolumeWell.py | 36 ++++++++++--------- 4 files changed, 76 insertions(+), 68 deletions(-) diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py index c50e5c222..e070549c3 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file -import os +from pathlib import Path import sys import numpy as np @@ -11,12 +11,12 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -dir_path = os.path.dirname( os.path.realpath( __file__ ) ) -parent_dir_path = os.path.dirname( dir_path ) -if parent_dir_path not in sys.path: - sys.path.append( parent_dir_path ) +# update sys.path to load all GEOS Python Package dependencies +geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent +sys.path.insert( 0, str( geos_pv_path / "src" ) ) +from geos.pv.utils.config import update_paths -import PVplugins # noqa: F401 +update_paths() from geos.utils.Logger import Logger, getLogger from geos.utils.PhysicalConstants import ( @@ -29,9 +29,12 @@ from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, ) +from paraview.detail.loghandler import ( # type: ignore[import-not-found] + VTKHandler, +) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py from PVplugins.PVExtractMergeBlocksVolume import PVExtractMergeBlocksVolume -from PVplugins.PVGeomechanicsAnalysis import PVGeomechanicsAnalysis +from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator __doc__ = """ PVGeomechanicsWorkflowVolume is a Paraview plugin that execute multiple filters @@ -369,15 +372,14 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: Returns: bool: True if calculation successfully eneded, False otherwise. """ - filter = PVGeomechanicsAnalysis() - filter.SetInputDataObject( self.m_volumeMesh ) - filter.b01SetGrainBulkModulus( self.getGrainBulkModulus() ) - filter.b02SetSpecificDensity( self.getSpecificDensity() ) - filter.d01SetRockCohesion( self.getRockCohesion() ) - filter.d02SetFrictionAngle( self.getFrictionAngle() ) - filter.c01SetAdvancedOutputs( self.m_computeAdvancedOutputs ) - filter.SetLogger( self.m_logger ) - filter.Update() - self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) ) + filter = GeomechanicsCalculator( self.m_volumeMesh, computeAdvancedOutputs=self.getComputeAdvancedOutputs(), speHandler=True ) + if not filter.logger.hasHandlers(): + filter.setLoggerHandler( VTKHandler() ) + filter.setGrainBulkModulus( self.getGrainBulkModulus() ) + filter.setSpecificDensity( self.getSpecificDensity() ) + filter.setRockCohesion( self.getRockCohesion() ) + filter.setFrictionAngle( self.getFrictionAngle() ) + filter.applyFilter() + self.m_volumeMesh.ShallowCopy( filter.getOutput() ) self.m_volumeMesh.Modified() return True diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py index 0bfae2ee2..01b28aadf 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file -import os +from pathlib import Path import sys import numpy as np @@ -11,16 +11,19 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -dir_path = os.path.dirname( os.path.realpath( __file__ ) ) -parent_dir_path = os.path.dirname( dir_path ) -if parent_dir_path not in sys.path: - sys.path.append( parent_dir_path ) +# update sys.path to load all GEOS Python Package dependencies +geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent +sys.path.insert( 0, str( geos_pv_path / "src" ) ) +from geos.pv.utils.config import update_paths -import PVplugins # noqa: F401 +update_paths() from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, ) +from paraview.detail.loghandler import ( # type: ignore[import-not-found] + VTKHandler, +) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py from geos.utils.Logger import Logger, getLogger from geos.utils.PhysicalConstants import ( @@ -32,7 +35,7 @@ ) from PVplugins.PVExtractMergeBlocksVolumeSurface import ( PVExtractMergeBlocksVolumeSurface, ) -from PVplugins.PVGeomechanicsAnalysis import PVGeomechanicsAnalysis +from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator from PVplugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics __doc__ = """ @@ -370,16 +373,15 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: Returns: bool: True if calculation successfully eneded, False otherwise. """ - filter = PVGeomechanicsAnalysis() - filter.SetInputDataObject( self.m_volumeMesh ) - filter.b01SetGrainBulkModulus( self.getGrainBulkModulus() ) - filter.b02SetSpecificDensity( self.getSpecificDensity() ) - filter.d01SetRockCohesion( self.getRockCohesion() ) - filter.d02SetFrictionAngle( self.getFrictionAngle() ) - filter.c01SetAdvancedOutputs( self.m_computeAdvancedOutputs ) - filter.SetLogger( self.m_logger ) - filter.Update() - self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) ) + filter = GeomechanicsCalculator( self.m_volumeMesh, computeAdvancedOutputs=self.getComputeAdvancedOutputs(), speHandler=True ) + if not filter.logger.hasHandlers(): + filter.setLoggerHandler( VTKHandler() ) + filter.setGrainBulkModulus( self.getGrainBulkModulus() ) + filter.setSpecificDensity( self.getSpecificDensity() ) + filter.setRockCohesion( self.getRockCohesion() ) + filter.setFrictionAngle( self.getFrictionAngle() ) + filter.applyFilter() + self.m_volumeMesh.ShallowCopy( filter.getOutput() ) self.m_volumeMesh.Modified() return True diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py index fff908568..18180b8dd 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file -import os +from pathlib import Path import sys import numpy as np @@ -11,16 +11,19 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -dir_path = os.path.dirname( os.path.realpath( __file__ ) ) -parent_dir_path = os.path.dirname( dir_path ) -if parent_dir_path not in sys.path: - sys.path.append( parent_dir_path ) +# update sys.path to load all GEOS Python Package dependencies +geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent +sys.path.insert( 0, str( geos_pv_path / "src" ) ) +from geos.pv.utils.config import update_paths -import PVplugins # noqa: F401 +update_paths() from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, ) +from paraview.detail.loghandler import ( # type: ignore[import-not-found] + VTKHandler, +) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py from geos.utils.Logger import Logger, getLogger from geos.utils.PhysicalConstants import ( @@ -32,7 +35,7 @@ ) from PVplugins.PVExtractMergeBlocksVolumeSurfaceWell import ( PVExtractMergeBlocksVolumeSurfaceWell, ) -from PVplugins.PVGeomechanicsAnalysis import PVGeomechanicsAnalysis +from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator from PVplugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics __doc__ = """ @@ -379,16 +382,15 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: Returns: bool: True if calculation successfully eneded, False otherwise. """ - filter = PVGeomechanicsAnalysis() - filter.SetInputDataObject( self.m_volumeMesh ) - filter.b01SetGrainBulkModulus( self.getGrainBulkModulus() ) - filter.b02SetSpecificDensity( self.getSpecificDensity() ) - filter.d01SetRockCohesion( self.getRockCohesion() ) - filter.d02SetFrictionAngle( self.getFrictionAngle() ) - filter.c01SetAdvancedOutputs( self.m_computeAdvancedOutputs ) - filter.SetLogger( self.m_logger ) - filter.Update() - self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) ) + filter = GeomechanicsCalculator( self.m_volumeMesh, computeAdvancedOutputs=self.getComputeAdvancedOutputs(), speHandler=True ) + if not filter.logger.hasHandlers(): + filter.setLoggerHandler( VTKHandler() ) + filter.setGrainBulkModulus( self.getGrainBulkModulus() ) + filter.setSpecificDensity( self.getSpecificDensity() ) + filter.setRockCohesion( self.getRockCohesion() ) + filter.setFrictionAngle( self.getFrictionAngle() ) + filter.applyFilter() + self.m_volumeMesh.ShallowCopy( filter.getOutput() ) self.m_volumeMesh.Modified() return True diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py index 210fd9336..eb15ba221 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file -import os +from pathlib import Path import sys import numpy as np @@ -11,16 +11,19 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -dir_path = os.path.dirname( os.path.realpath( __file__ ) ) -parent_dir_path = os.path.dirname( dir_path ) -if parent_dir_path not in sys.path: - sys.path.append( parent_dir_path ) +# update sys.path to load all GEOS Python Package dependencies +geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent +sys.path.insert( 0, str( geos_pv_path / "src" ) ) +from geos.pv.utils.config import update_paths -import PVplugins # noqa: F401 +update_paths() from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, ) +from paraview.detail.loghandler import ( # type: ignore[import-not-found] + VTKHandler, +) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py from geos.utils.Logger import Logger, getLogger from geos.utils.PhysicalConstants import ( @@ -33,7 +36,7 @@ from PVplugins.PVExtractMergeBlocksVolumeWell import ( PVExtractMergeBlocksVolumeWell, ) -from PVplugins.PVGeomechanicsAnalysis import PVGeomechanicsAnalysis +from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator __doc__ = """ PVGeomechanicsWorkflowVolumeWell is a Paraview plugin that execute @@ -384,15 +387,14 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: Returns: bool: True if calculation successfully eneded, False otherwise. """ - filter = PVGeomechanicsAnalysis() - filter.SetInputDataObject( self.m_volumeMesh ) - filter.b01SetGrainBulkModulus( self.getGrainBulkModulus() ) - filter.b02SetSpecificDensity( self.getSpecificDensity() ) - filter.d01SetRockCohesion( self.getRockCohesion() ) - filter.d02SetFrictionAngle( self.getFrictionAngle() ) - filter.c01SetAdvancedOutputs( self.m_computeAdvancedOutputs ) - filter.SetLogger( self.m_logger ) - filter.Update() - self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) ) + filter = GeomechanicsCalculator( self.m_volumeMesh, computeAdvancedOutputs=self.getComputeAdvancedOutputs(), speHandler=True ) + if not filter.logger.hasHandlers(): + filter.setLoggerHandler( VTKHandler() ) + filter.setGrainBulkModulus( self.getGrainBulkModulus() ) + filter.setSpecificDensity( self.getSpecificDensity() ) + filter.setRockCohesion( self.getRockCohesion() ) + filter.setFrictionAngle( self.getFrictionAngle() ) + filter.applyFilter() + self.m_volumeMesh.ShallowCopy( filter.getOutput() ) self.m_volumeMesh.Modified() return True From 0e827b707d189b2be8cbec0562c5925d0cd42c44 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Wed, 24 Sep 2025 18:37:19 +0200 Subject: [PATCH 23/40] fix the test --- .../tests/testsFunctionsGeomechanicsCalculator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/geos-geomechanics/tests/testsFunctionsGeomechanicsCalculator.py b/geos-geomechanics/tests/testsFunctionsGeomechanicsCalculator.py index 8ea4f11de..7fc8b4b5a 100644 --- a/geos-geomechanics/tests/testsFunctionsGeomechanicsCalculator.py +++ b/geos-geomechanics/tests/testsFunctionsGeomechanicsCalculator.py @@ -181,11 +181,11 @@ def test_ReservoirStressPathReal( self: Self ) -> None: deltaPressure: npt.NDArray[ np.float64 ] = 0.2 * pressure obtained: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathReal( deltaStress, deltaPressure ) expected: npt.NDArray[ np.float64 ] = np.array( [ - [ 8.0, 12.5, 9.0 ], - [ 0.32, 0.46, 0.34 ], - [ np.nan, np.nan, np.nan ], - [ 1.38, 1.81, 1.38 ], - [ 1.30, 1.55, 1.15 ], + [ 8.0, 12.5, 9.0, np.nan, 10.0, 9.5 ], + [ 0.32, 0.46, 0.34, np.nan, 0.39, 0.38 ], + [ np.nan, np.nan, np.nan, np.nan, np.nan, np.nan ], + [ 1.38, 1.81, 1.38, np.nan, 1.56, 1.56 ], + [ 1.30, 1.55, 1.15, np.nan, 1.45, 1.35 ], ] ) self.assertTrue( np.array_equal( np.round( obtained, 2 ), np.round( expected, 2 ), equal_nan=True ) ) From 26883dbba7c5c8d195636e4fed0eeb2a4db891d9 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Wed, 24 Sep 2025 18:41:27 +0200 Subject: [PATCH 24/40] fix the ci --- geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py | 4 +++- .../src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py | 4 +++- .../src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py | 4 +++- geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py index e070549c3..f250c6a83 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py @@ -372,7 +372,9 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: Returns: bool: True if calculation successfully eneded, False otherwise. """ - filter = GeomechanicsCalculator( self.m_volumeMesh, computeAdvancedOutputs=self.getComputeAdvancedOutputs(), speHandler=True ) + filter = GeomechanicsCalculator( self.m_volumeMesh, + computeAdvancedOutputs=self.getComputeAdvancedOutputs(), + speHandler=True ) if not filter.logger.hasHandlers(): filter.setLoggerHandler( VTKHandler() ) filter.setGrainBulkModulus( self.getGrainBulkModulus() ) diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py index 01b28aadf..edf35a699 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py @@ -373,7 +373,9 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: Returns: bool: True if calculation successfully eneded, False otherwise. """ - filter = GeomechanicsCalculator( self.m_volumeMesh, computeAdvancedOutputs=self.getComputeAdvancedOutputs(), speHandler=True ) + filter = GeomechanicsCalculator( self.m_volumeMesh, + computeAdvancedOutputs=self.getComputeAdvancedOutputs(), + speHandler=True ) if not filter.logger.hasHandlers(): filter.setLoggerHandler( VTKHandler() ) filter.setGrainBulkModulus( self.getGrainBulkModulus() ) diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py index 18180b8dd..95c708899 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py @@ -382,7 +382,9 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: Returns: bool: True if calculation successfully eneded, False otherwise. """ - filter = GeomechanicsCalculator( self.m_volumeMesh, computeAdvancedOutputs=self.getComputeAdvancedOutputs(), speHandler=True ) + filter = GeomechanicsCalculator( self.m_volumeMesh, + computeAdvancedOutputs=self.getComputeAdvancedOutputs(), + speHandler=True ) if not filter.logger.hasHandlers(): filter.setLoggerHandler( VTKHandler() ) filter.setGrainBulkModulus( self.getGrainBulkModulus() ) diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py index eb15ba221..b68545e39 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py @@ -387,7 +387,9 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: Returns: bool: True if calculation successfully eneded, False otherwise. """ - filter = GeomechanicsCalculator( self.m_volumeMesh, computeAdvancedOutputs=self.getComputeAdvancedOutputs(), speHandler=True ) + filter = GeomechanicsCalculator( self.m_volumeMesh, + computeAdvancedOutputs=self.getComputeAdvancedOutputs(), + speHandler=True ) if not filter.logger.hasHandlers(): filter.setLoggerHandler( VTKHandler() ) filter.setGrainBulkModulus( self.getGrainBulkModulus() ) From afa427f7c62a91b6e5b6a92145b93f3e5ff37cda Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Fri, 26 Sep 2025 11:35:09 +0200 Subject: [PATCH 25/40] Add the new folder geos-processing --- docs/geos-processing.rst | 13 ++++ .../generic_processing_tool.rst | 4 ++ docs/geos_processing_docs/post_processing.rst | 7 ++ docs/geos_processing_docs/pre_processing.rst | 4 ++ docs/index.rst | 6 +- geos-processing/pyproject.toml | 70 +++++++++++++++++++ geos-pv/requirements.txt | 3 +- install_packages.sh | 1 + 8 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 docs/geos-processing.rst create mode 100644 docs/geos_processing_docs/generic_processing_tool.rst create mode 100644 docs/geos_processing_docs/post_processing.rst create mode 100644 docs/geos_processing_docs/pre_processing.rst create mode 100644 geos-processing/pyproject.toml diff --git a/docs/geos-processing.rst b/docs/geos-processing.rst new file mode 100644 index 000000000..735071d93 --- /dev/null +++ b/docs/geos-processing.rst @@ -0,0 +1,13 @@ +GEOS Processing tools +======================== + + +.. toctree:: + :maxdepth: 5 + :caption: Contents: + + ./geos_processing_docs/post_processing.rst + + ./geos_processing_docs/pre_processing.rst + + ./geos_processing_docs/generic_processing_tools.rst diff --git a/docs/geos_processing_docs/generic_processing_tool.rst b/docs/geos_processing_docs/generic_processing_tool.rst new file mode 100644 index 000000000..4fdb32fcb --- /dev/null +++ b/docs/geos_processing_docs/generic_processing_tool.rst @@ -0,0 +1,4 @@ +Generic processing filters +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In progress.. \ No newline at end of file diff --git a/docs/geos_processing_docs/post_processing.rst b/docs/geos_processing_docs/post_processing.rst new file mode 100644 index 000000000..701cb96c5 --- /dev/null +++ b/docs/geos_processing_docs/post_processing.rst @@ -0,0 +1,7 @@ +Post-processing filters +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This module contains GEOS post-processing tools. + +Geomechanics post-processing tools +===================================== \ No newline at end of file diff --git a/docs/geos_processing_docs/pre_processing.rst b/docs/geos_processing_docs/pre_processing.rst new file mode 100644 index 000000000..859730930 --- /dev/null +++ b/docs/geos_processing_docs/pre_processing.rst @@ -0,0 +1,4 @@ +Pre-processing filters +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In progress.. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 536616d1a..034d0e45b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -47,7 +47,7 @@ To do this, you can clone a copy of the geosPythonPackages repository and instal cd geosPythonPackages/ python -m pip install --upgrade geos-ats - + .. note:: To upgrade an existing installation, the python executable in the above command should correspond to the version you indicated in your host config. If you have previously built the tools, this version will be linked in the build directory: `build_dir/bin/python`. @@ -87,7 +87,9 @@ Packages geos-mesh geos-posp - + + geos-processing + geos-pv geos-timehistory diff --git a/geos-processing/pyproject.toml b/geos-processing/pyproject.toml new file mode 100644 index 000000000..71fadf3ed --- /dev/null +++ b/geos-processing/pyproject.toml @@ -0,0 +1,70 @@ +[build-system] +requires = ["setuptools>=61.2", "wheel >= 0.37.1"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages.find] +where = ["src"] +include = ["geos.processing*"] +exclude = ['tests*'] + +[project] +name = "geos-processing" +version = "0.1.0" +description = "GEOS post- and pre-processing tools" +authors = [{name = "GEOS Contributors" }] +maintainers = [ + {name = "Alexandre Benedicto", email = "alexandre.benedicto@external.totalenergies.com" }, + {name = "Romain Baville", email = "romain.baville@external.totalenergies.com" }, + {name = "Paloma Martinez", email = "paloma.martinez@external.totalenergies.com" }, +] +license = {text = "Apache-2.0"} +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python" +] + +requires-python = ">=3.10" + +dependencies = [ + "geos-geomechanics", + "geos-utils", + "geos-mesh", + "vtk >= 9.3", + "numpy >= 2.2", + "typing_extensions >= 4.12", +] + + +[project.urls] +Homepage = "https://github.com/GEOS-DEV/geosPythonPackages" +Documentation = "https://geosx-geosx.readthedocs-hosted.com/projects/geosx-geospythonpackages/en/latest/" +Repository = "https://github.com/GEOS-DEV/geosPythonPackages.git" +"Bug Tracker" = "https://github.com/GEOS-DEV/geosPythonPackages/issues" + +[project.optional-dependencies] +build = [ + "build ~= 1.2" +] +dev = [ + "mypy", + "ruff", + "yapf", +] +test = [ + "pytest-cov", + "pytest" +] + +[tool.pytest.ini_options] +addopts = "--import-mode=importlib" +console_output_style = "count" +pythonpath = ["src"] +python_classes = "Test" +python_files = "test*.py" +python_functions = "test*" +testpaths = ["tests"] +norecursedirs = "bin" +filterwarnings = [] \ No newline at end of file diff --git a/geos-pv/requirements.txt b/geos-pv/requirements.txt index edb1046c0..6ec1b5a97 100644 --- a/geos-pv/requirements.txt +++ b/geos-pv/requirements.txt @@ -1,4 +1,5 @@ geos-geomechanics geos-mesh geos-posp -geos-utils \ No newline at end of file +geos-utils +geos-processing \ No newline at end of file diff --git a/install_packages.sh b/install_packages.sh index 74238daf0..e71602286 100755 --- a/install_packages.sh +++ b/install_packages.sh @@ -3,6 +3,7 @@ python -m pip install --upgrade ./geos-utils python -m pip install --upgrade ./geos-geomechanics python -m pip install --upgrade ./geos-mesh python -m pip install --upgrade ./geos-posp +python -m pip install --upgrade ./geos-processing python -m pip install --upgrade ./geos-xml-tools python -m pip install --upgrade ./geos-xml-viewer python -m pip install --upgrade ./hdf5-wrapper From f09f60da118e44c1bb55dc138618322f6d978462 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Fri, 26 Sep 2025 11:50:17 +0200 Subject: [PATCH 26/40] move GeomechanicsCalculators to geos-processing --- docs/geos_mesh_docs/processing.rst | 8 -------- ...rocessing_tool.rst => generic_processing_tools.rst} | 0 docs/geos_processing_docs/post_processing.rst | 10 +++++++++- .../src/PVplugins/PVGeomechanicsWorkflowVolume.py | 2 +- .../PVplugins/PVGeomechanicsWorkflowVolumeSurface.py | 2 +- .../PVGeomechanicsWorkflowVolumeSurfaceWell.py | 2 +- .../src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py | 2 +- .../post_processing}/GeomechanicsCalculator.py | 0 .../src/geos/pv/plugins/PVGeomechanicsCalculator.py | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) rename docs/geos_processing_docs/{generic_processing_tool.rst => generic_processing_tools.rst} (100%) rename {geos-mesh/src/geos/mesh/processing => geos-processing/src/geos/processing/post_processing}/GeomechanicsCalculator.py (100%) diff --git a/docs/geos_mesh_docs/processing.rst b/docs/geos_mesh_docs/processing.rst index 79eac2ba9..5a5a6230d 100644 --- a/docs/geos_mesh_docs/processing.rst +++ b/docs/geos_mesh_docs/processing.rst @@ -19,14 +19,6 @@ geos.mesh.processing.FillPartialArrays filter :undoc-members: :show-inheritance: -geos.mesh.processing.GeomechanicsCalculator module ---------------------------------------------------- - -.. automodule:: geos.mesh.processing.GeomechanicsCalculator - :members: - :undoc-members: - :show-inheritance: - geos.mesh.processing.meshQualityMetricHelpers module ----------------------------------------------------- diff --git a/docs/geos_processing_docs/generic_processing_tool.rst b/docs/geos_processing_docs/generic_processing_tools.rst similarity index 100% rename from docs/geos_processing_docs/generic_processing_tool.rst rename to docs/geos_processing_docs/generic_processing_tools.rst diff --git a/docs/geos_processing_docs/post_processing.rst b/docs/geos_processing_docs/post_processing.rst index 701cb96c5..b376bdbf9 100644 --- a/docs/geos_processing_docs/post_processing.rst +++ b/docs/geos_processing_docs/post_processing.rst @@ -4,4 +4,12 @@ Post-processing filters This module contains GEOS post-processing tools. Geomechanics post-processing tools -===================================== \ No newline at end of file +===================================== + +geos.processing.post_processing.GeomechanicsCalculator module +-------------------------------------------------------------- + +.. automodule:: geos.processing.post_processing.GeomechanicsCalculator + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py index f250c6a83..acb71913b 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py @@ -34,7 +34,7 @@ ) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py from PVplugins.PVExtractMergeBlocksVolume import PVExtractMergeBlocksVolume -from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator +from geos.processing.post_processing.GeomechanicsCalculator import GeomechanicsCalculator __doc__ = """ PVGeomechanicsWorkflowVolume is a Paraview plugin that execute multiple filters diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py index edf35a699..530f4f38f 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py @@ -35,7 +35,7 @@ ) from PVplugins.PVExtractMergeBlocksVolumeSurface import ( PVExtractMergeBlocksVolumeSurface, ) -from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator +from geos.processing.post_processing.GeomechanicsCalculator import GeomechanicsCalculator from PVplugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics __doc__ = """ diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py index 95c708899..4cf748089 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py @@ -35,7 +35,7 @@ ) from PVplugins.PVExtractMergeBlocksVolumeSurfaceWell import ( PVExtractMergeBlocksVolumeSurfaceWell, ) -from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator +from geos.processing.post_processing.GeomechanicsCalculator import GeomechanicsCalculator from PVplugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics __doc__ = """ diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py index b68545e39..58a6fe2d6 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py @@ -36,7 +36,7 @@ from PVplugins.PVExtractMergeBlocksVolumeWell import ( PVExtractMergeBlocksVolumeWell, ) -from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator +from geos.processing.post_processing.GeomechanicsCalculator import GeomechanicsCalculator __doc__ = """ PVGeomechanicsWorkflowVolumeWell is a Paraview plugin that execute diff --git a/geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py similarity index 100% rename from geos-mesh/src/geos/mesh/processing/GeomechanicsCalculator.py rename to geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py index 63d266d41..a5b1fb209 100644 --- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py +++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py @@ -30,7 +30,7 @@ DEFAULT_ROCK_COHESION, WATER_DENSITY, ) -from geos.mesh.processing.GeomechanicsCalculator import GeomechanicsCalculator +from geos.processing.post_processing.GeomechanicsCalculator import GeomechanicsCalculator __doc__ = """ PVGeomechanicsCalculator is a paraview plugin that allows to compute additional From 51889210f32c9de70ce97a8f756e0909b4943cdf Mon Sep 17 00:00:00 2001 From: Romain Baville <126683264+RomainBaville@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:21:43 +0200 Subject: [PATCH 27/40] Apply suggestions from Paloma's review Co-authored-by: paloma-martinez <104762252+paloma-martinez@users.noreply.github.com> --- .../post_processing/GeomechanicsCalculator.py | 11 +++++++---- .../src/geos/pv/plugins/PVGeomechanicsCalculator.py | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py index 26fad5c56..9e53133e4 100644 --- a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py +++ b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py @@ -60,12 +60,15 @@ GeomechanicsCalculator filter input mesh is either vtkPointSet or vtkUnstructuredGrid and returned mesh is of same type as input. + .. Important:: + Please refer to the GeosExtractMergeBlockVolume* filters to provide the correct input. + .. Note:: - The default physical constants used by the filter are: - - grainBulkModulus = 38e9 Pa (the one of the Quartz) - - specificDensity = 1000.0 kg/m³ (the one of the water) - - rockCohesion = 0.0 Pa - - frictionAngle = 10.0 / 180.0 * np.pi rad + - grainBulkModulus = 38e9 Pa ( quartz value ) + - specificDensity = 1000.0 kg/m³ ( water value ) + - rockCohesion = 0.0 Pa ( fractured case ) + - frictionAngle = 10.0° To use the filter: diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py index a5b1fb209..f4d861c5d 100644 --- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py +++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py @@ -43,7 +43,7 @@ - Specific gravity - Real effective stress ratio - Total initial stress, total current stress and total stress ratio - - Lithostatic stress (physic to update) + - Lithostatic stress (physics to update) - Elastic stain - Reservoir stress path and reservoir stress path in oedometric condition @@ -54,8 +54,8 @@ PVGeomechanicsCalculator paraview plugin input mesh is either vtkPointSet or vtkUnstructuredGrid and returned mesh is of same type as input. -.. Note:: - To deals with Geos output, you may first process it with PVExtractMergeBlocksVolume +.. Important:: + Please refer to the PVGeosExtractMergeBlockVolume* plugins to provide the correct input. To use it: From 2629ad2850a11c09c75409b05ccf62dece65e0d7 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 7 Oct 2025 15:36:46 +0200 Subject: [PATCH 28/40] Keep the old way to load the file, this update is for another pr --- .../PVplugins/PVGeomechanicsWorkflowVolume.py | 16 +++++++++------- .../PVGeomechanicsWorkflowVolumeSurface.py | 13 +++++++------ .../PVGeomechanicsWorkflowVolumeSurfaceWell.py | 13 +++++++------ .../PVGeomechanicsWorkflowVolumeWell.py | 12 ++++++------ 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py index acb71913b..2a1a30ba8 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file -from pathlib import Path +import os import sys import numpy as np @@ -11,12 +11,13 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -# update sys.path to load all GEOS Python Package dependencies -geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent -sys.path.insert( 0, str( geos_pv_path / "src" ) ) -from geos.pv.utils.config import update_paths +# TODO: Change the way of import file, it is not working with Paraview 5.13 +dir_path = os.path.dirname( os.path.realpath( __file__ ) ) +parent_dir_path = os.path.dirname( dir_path ) +if parent_dir_path not in sys.path: + sys.path.append( parent_dir_path ) -update_paths() +import PVplugins # noqa: F401 from geos.utils.Logger import Logger, getLogger from geos.utils.PhysicalConstants import ( @@ -333,7 +334,8 @@ def RequestData( # 1. extract volume self.doExtractAndMerge() # 2. compute Geomechanical outputs in volume mesh - self.computeAdditionalOutputsVolume() + a = self.computeAdditionalOutputsVolume() + print(a) except AssertionError as e: mess: str = "Geomechanics workflow failed due to:" diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py index 530f4f38f..5aceef28d 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file -from pathlib import Path +import os import sys import numpy as np @@ -11,12 +11,13 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -# update sys.path to load all GEOS Python Package dependencies -geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent -sys.path.insert( 0, str( geos_pv_path / "src" ) ) -from geos.pv.utils.config import update_paths +# TODO: Change the way of import file, it is not working with Paraview 5.13 +dir_path = os.path.dirname( os.path.realpath( __file__ ) ) +parent_dir_path = os.path.dirname( dir_path ) +if parent_dir_path not in sys.path: + sys.path.append( parent_dir_path ) -update_paths() +import PVplugins # noqa: F401 from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py index 4cf748089..dc881d8d1 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file -from pathlib import Path +import os import sys import numpy as np @@ -11,12 +11,13 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -# update sys.path to load all GEOS Python Package dependencies -geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent -sys.path.insert( 0, str( geos_pv_path / "src" ) ) -from geos.pv.utils.config import update_paths +# TODO: Change the way of import file, it is not working with Paraview 5.13 +dir_path = os.path.dirname( os.path.realpath( __file__ ) ) +parent_dir_path = os.path.dirname( dir_path ) +if parent_dir_path not in sys.path: + sys.path.append( parent_dir_path ) -update_paths() +import PVplugins # noqa: F401 from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py index 58a6fe2d6..e6292b08f 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file -from pathlib import Path +import os import sys import numpy as np @@ -11,12 +11,12 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -# update sys.path to load all GEOS Python Package dependencies -geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent -sys.path.insert( 0, str( geos_pv_path / "src" ) ) -from geos.pv.utils.config import update_paths +dir_path = os.path.dirname( os.path.realpath( __file__ ) ) +parent_dir_path = os.path.dirname( dir_path ) +if parent_dir_path not in sys.path: + sys.path.append( parent_dir_path ) -update_paths() +import PVplugins # noqa: F401 from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, From 45d49c87f54842c9260dc256d8384c76847a6241 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 7 Oct 2025 15:51:20 +0200 Subject: [PATCH 29/40] fix the ci --- geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py | 4 ++-- .../src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py | 2 +- .../src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py | 2 +- geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py index 2a1a30ba8..f9954afd6 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py @@ -17,7 +17,7 @@ if parent_dir_path not in sys.path: sys.path.append( parent_dir_path ) -import PVplugins # noqa: F401 +import PVplugins # noqa: F401 from geos.utils.Logger import Logger, getLogger from geos.utils.PhysicalConstants import ( @@ -335,7 +335,7 @@ def RequestData( self.doExtractAndMerge() # 2. compute Geomechanical outputs in volume mesh a = self.computeAdditionalOutputsVolume() - print(a) + print( a ) except AssertionError as e: mess: str = "Geomechanics workflow failed due to:" diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py index 5aceef28d..26ca8ea8e 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py @@ -17,7 +17,7 @@ if parent_dir_path not in sys.path: sys.path.append( parent_dir_path ) -import PVplugins # noqa: F401 +import PVplugins # noqa: F401 from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py index dc881d8d1..8a69c4e16 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py @@ -17,7 +17,7 @@ if parent_dir_path not in sys.path: sys.path.append( parent_dir_path ) -import PVplugins # noqa: F401 +import PVplugins # noqa: F401 from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py index e6292b08f..f8ce3dad8 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py @@ -11,12 +11,13 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) +# TODO: Change the way of import file, it is not working with Paraview 5.13 dir_path = os.path.dirname( os.path.realpath( __file__ ) ) parent_dir_path = os.path.dirname( dir_path ) if parent_dir_path not in sys.path: sys.path.append( parent_dir_path ) -import PVplugins # noqa: F401 +import PVplugins # noqa: F401 from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, From 5022f88cc6e47d8993c1da6b3a8222dab554f581 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 7 Oct 2025 18:21:45 +0200 Subject: [PATCH 30/40] Factorize the checkMandatoryAttributesFunction and add docs --- .../post_processing/GeomechanicsCalculator.py | 177 ++++++++---------- 1 file changed, 81 insertions(+), 96 deletions(-) diff --git a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py index 9e53133e4..9a96f005f 100644 --- a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py +++ b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay, Romain Baville # ruff: noqa: E402 # disable Module level import not at top of file -from typing import Union +from typing import Union, Dict, Any from typing_extensions import Self import logging @@ -39,8 +39,16 @@ from vtkmodules.vtkFiltersCore import vtkCellCenters __doc__ = """ -GeomechanicsCalculator module is a vtk filter that allows to compute additional -Geomechanical properties from existing ones. +GeomechanicsCalculator module is a vtk filter that allows to compute additional geomechanical properties from existing ones: + - The Young modulus and the Poisson's ratio named "youngModulus" and "poissonRatio" or bulk and shear moduli named "bulkModulus" and "shearModulus" + - The initial Young modulus and Poisson's ratio named "youngModulusInitial" and "poissonRatioInitial" or the initial bulk modulus named "bulkModulusInitial" + - The porosity named "porosity" + - The initial porosity named "porosityInitial" + - The delta of pressure named "deltaPressure" + - The density named "density" + - The effective stress named "stressEffective" + - The initial effective stress named "stressEffectiveInitial" + - The pressure named "pressure" The basic geomechanics outputs are: - Elastic modulus (young modulus and poisson ratio or bulk modulus and shear modulus) @@ -54,7 +62,7 @@ - Reservoir stress path and reservoir stress path in oedometric condition The advanced geomechanics outputs are: - - fracture index and threshold + - Fracture index and threshold - Critical pore pressure and pressure index GeomechanicsCalculator filter input mesh is either vtkPointSet or vtkUnstructuredGrid @@ -239,13 +247,21 @@ def getOutputType( self: Self ) -> str: def checkMandatoryAttributes( self: Self ) -> bool: """Check that mandatory attributes are present in the mesh. - The mesh must contains either the young Modulus and Poisson's ratio - (computeYoungPoisson=False) or the bulk and shear moduli - (computeYoungPoisson=True) + The mesh must contains: + - The Young modulus and the Poisson's ratio named "youngModulus" and "poissonRatio" or bulk and shear moduli named "bulkModulus" and "shearModulus" + - The initial Young modulus and Poisson's ratio named "youngModulusInitial" and "poissonRatioInitial" or the initial bulk modulus named "bulkModulusInitial" + - The porosity named "porosity" + - The initial porosity named "porosityInitial" + - The delta of pressure named "deltaPressure" + - The density named "density" + - The effective stress named "stressEffective" + - The initial effective stress named "stressEffectiveInitial" + - The pressure named "pressure" Returns: bool: True if all needed attributes are present, False otherwise """ + # Check the presence of the elastic moduli at the current time. self.youngModulusAttributeName: str = PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName self.youngModulusOnPoints: bool = PostProcessingOutputsEnum.YOUNG_MODULUS.isOnPoints youngModulusOnMesh: bool = isAttributeInObject( self.output, self.youngModulusAttributeName, @@ -253,8 +269,8 @@ def checkMandatoryAttributes( self: Self ) -> bool: self.poissonRatioAttributeName: str = PostProcessingOutputsEnum.POISSON_RATIO.attributeName self.poissonRatioOnPoints: bool = PostProcessingOutputsEnum.POISSON_RATIO.isOnPoints - poissonRationOnMesh: bool = isAttributeInObject( self.output, self.poissonRatioAttributeName, - self.poissonRatioOnPoints ) + poissonRatioOnMesh: bool = isAttributeInObject( self.output, self.poissonRatioAttributeName, + self.poissonRatioOnPoints ) self.bulkModulusAttributeName: str = GeosMeshOutputsEnum.BULK_MODULUS.attributeName self.bulkModulusOnPoints: bool = GeosMeshOutputsEnum.BULK_MODULUS.isOnPoints @@ -266,7 +282,8 @@ def checkMandatoryAttributes( self: Self ) -> bool: shearModulusOnMesh: bool = isAttributeInObject( self.output, self.shearModulusAttributeName, self.shearModulusOnPoints ) - if not youngModulusOnMesh and not poissonRationOnMesh: + self.computeYoungPoisson: bool + if not youngModulusOnMesh and not poissonRatioOnMesh: if bulkModulusOnMesh and shearModulusOnMesh: self.computeYoungPoisson = True else: @@ -275,93 +292,74 @@ def checkMandatoryAttributes( self: Self ) -> bool: ) return False elif not bulkModulusOnMesh and not shearModulusOnMesh: - if youngModulusOnMesh and poissonRationOnMesh: + if youngModulusOnMesh and poissonRatioOnMesh: self.computeYoungPoisson = False else: self.logger.error( f"{ self.youngModulusAttributeName } or { self.poissonRatioAttributeName } are missing to compute geomechanical outputs." ) + return False else: self.logger.error( f"{ self.bulkModulusAttributeName } and { self.shearModulusAttributeName } or { self.youngModulusAttributeName } and { self.poissonRatioAttributeName } are mandatory to compute geomechanical outputs." ) return False - porosityAttributeName: str = GeosMeshOutputsEnum.POROSITY.attributeName - porosityOnPoints: bool = GeosMeshOutputsEnum.POROSITY.isOnPoints - if not isAttributeInObject( self.output, porosityAttributeName, porosityOnPoints ): - self.logger.error( - f"The mandatory attribute { porosityAttributeName } is missing to compute geomechanical outputs." ) - return False - else: - self.porosity: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, porosityAttributeName, - porosityOnPoints ) + # Check the presence of the elastic moduli at the initial time. + self.bulkModulusT0AttributeName: str = PostProcessingOutputsEnum.BULK_MODULUS_INITIAL.attributeName + self.bulkModulusT0OnPoints: bool = PostProcessingOutputsEnum.BULK_MODULUS_INITIAL.isOnPoints + bulkModulusT0OnMesh: bool = isAttributeInObject( self.output, self.bulkModulusT0AttributeName, + self.bulkModulusT0OnPoints ) - porosityInitialAttributeName: str = GeosMeshOutputsEnum.POROSITY_INI.attributeName - porosityInitialOnPoints: bool = GeosMeshOutputsEnum.POROSITY_INI.isOnPoints - if not isAttributeInObject( self.output, porosityInitialAttributeName, porosityInitialOnPoints ): - self.logger.error( - f"The mandatory attribute { porosityInitialAttributeName } is missing to compute geomechanical outputs." - ) - return False - else: - self.porosityInitial: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, - porosityInitialAttributeName, - porosityInitialOnPoints ) + self.youngModulusT0AttributeName: str = PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.attributeName + self.youngModulusT0OnPoints: bool = PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.isOnPoints + youngModulusT0OnMesh: bool = isAttributeInObject( self.output, self.youngModulusT0AttributeName, + self.youngModulusT0OnPoints ) - deltaPressureAttributeName: str = GeosMeshOutputsEnum.DELTA_PRESSURE.attributeName - deltaPressureOnPoint: bool = GeosMeshOutputsEnum.DELTA_PRESSURE.isOnPoints - if not isAttributeInObject( self.output, deltaPressureAttributeName, deltaPressureOnPoint ): - self.logger.error( - f"The mandatory attribute { deltaPressureAttributeName } is missing to compute geomechanical outputs." ) - return False - else: - self.deltaPressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, deltaPressureAttributeName, - deltaPressureOnPoint ) + self.poissonRatioT0AttributeName: str = PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.attributeName + self.poissonRatioT0OnPoints: bool = PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.isOnPoints + poissonRatioT0OnMesh: bool = isAttributeInObject( self.output, self.poissonRatioT0AttributeName, + self.poissonRatioT0OnPoints ) - densityAttributeName: str = GeosMeshOutputsEnum.ROCK_DENSITY.attributeName - densityOnPoints: bool = GeosMeshOutputsEnum.ROCK_DENSITY.isOnPoints - if not isAttributeInObject( self.output, densityAttributeName, densityOnPoints ): - self.logger.error( - f"The mandatory attribute { densityAttributeName } is missing to compute geomechanical outputs." ) - return False + self.computeInitialBulkModulus: bool + if bulkModulusT0OnMesh: + self.computeInitialBulkModulus = False + elif youngModulusT0OnMesh and poissonRatioT0OnMesh: + self.computeInitialBulkModulus = True else: - self.density: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, densityAttributeName, - densityOnPoints ) - - effectiveStressAttributeName: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName - effectiveStressOnPoints: str = GeosMeshOutputsEnum.STRESS_EFFECTIVE.isOnPoints - if not isAttributeInObject( self.output, effectiveStressAttributeName, effectiveStressOnPoints ): self.logger.error( - f"The mandatory attribute { effectiveStressAttributeName } is missing to compute geomechanical outputs." + f"{ self.bulkModulusT0AttributeName } or { self.youngModulusT0AttributeName } and { self.poissonRatioT0AttributeName } are mandatory to compute geomechanical outputs." ) return False - else: - self.effectiveStress: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, - effectiveStressAttributeName, - effectiveStressOnPoints ) - effectiveStressT0AttributeName: str = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.attributeName - effectiveStressT0OnPoints: bool = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL.isOnPoints - if not isAttributeInObject( self.output, effectiveStressT0AttributeName, effectiveStressT0OnPoints ): - self.logger.error( - f"The mandatory attribute { effectiveStressT0AttributeName } is missing to compute geomechanical outputs." - ) - return False - else: - self.effectiveStressT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, - effectiveStressT0AttributeName, - effectiveStressT0OnPoints ) + # Check the presence of the other mandatory attributes + dictMandatoryAttribute: Dict[ Any, Any ] = { + GeosMeshOutputsEnum.POROSITY: None, + GeosMeshOutputsEnum.POROSITY_INI: None, + GeosMeshOutputsEnum.DELTA_PRESSURE: None, + GeosMeshOutputsEnum.ROCK_DENSITY: None, + GeosMeshOutputsEnum.STRESS_EFFECTIVE: None, + PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL: None, + GeosMeshOutputsEnum.PRESSURE: None, + } + for mandatoryAttribute in dictMandatoryAttribute: + attributeName: str = mandatoryAttribute.attributeName + onPoints: bool = mandatoryAttribute.isOnPoints + if not isAttributeInObject( self.output, attributeName, onPoints ): + self.logger.error( + f"The mandatory attribute { attributeName } is missing to compute geomechanical outputs." ) + return False + else: + dictMandatoryAttribute[ mandatoryAttribute ] = getArrayInObject( self.output, attributeName, onPoints ) - pressureAttributeName: str = GeosMeshOutputsEnum.PRESSURE.attributeName - pressureOnPoints: bool = GeosMeshOutputsEnum.PRESSURE.isOnPoints - if not isAttributeInObject( self.output, pressureAttributeName, pressureOnPoints ): - self.logger.error( - f"The mandatory attribute { pressureAttributeName } is missing to compute geomechanical outputs." ) - return False - else: - self.pressure: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, pressureAttributeName, - pressureOnPoints ) + self.porosity: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.POROSITY ] + self.porosityInitial: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.POROSITY_INI ] + self.deltaPressure: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.DELTA_PRESSURE ] + self.density: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.ROCK_DENSITY ] + self.effectiveStress: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.STRESS_EFFECTIVE ] + self.effectiveStressT0: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ + PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL ] + self.pressure: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.PRESSURE ] return True @@ -666,28 +664,15 @@ def computeTotalStressInitial( self: Self ) -> bool: Returns: bool: True if calculation successfully ended, False otherwise. """ - bulkModulusT0AttributeName: str = PostProcessingOutputsEnum.BULK_MODULUS_INITIAL.attributeName - youngModulusT0AttributeName: str = PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.attributeName - poissonRatioT0AttributeName: str = PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.attributeName - - bulkModulusT0OnPoints: bool = PostProcessingOutputsEnum.BULK_MODULUS_INITIAL.isOnPoints - youngModulusT0OnPoints: bool = PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.isOnPoints - poissonRatioT0OnPoints: bool = PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.isOnPoints - - # Compute BulkModulus at initial time step. bulkModulusT0: npt.NDArray[ np.float64 ] - if isAttributeInObject( self.output, bulkModulusT0AttributeName, bulkModulusT0OnPoints ): - bulkModulusT0 = getArrayInObject( self.output, bulkModulusT0AttributeName, bulkModulusT0OnPoints ) - elif isAttributeInObject( self.output, youngModulusT0AttributeName, youngModulusT0OnPoints ) \ - and isAttributeInObject( self.output, poissonRatioT0AttributeName, poissonRatioT0OnPoints ): - youngModulusT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, youngModulusT0AttributeName, - youngModulusT0OnPoints ) - poissonRatioT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, poissonRatioT0AttributeName, - poissonRatioT0OnPoints ) + if self.computeInitialBulkModulus: + youngModulusT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, self.youngModulusT0AttributeName, + self.youngModulusT0OnPoints ) + poissonRatioT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, self.poissonRatioT0AttributeName, + self.poissonRatioT0OnPoints ) bulkModulusT0 = fcts.bulkModulus( youngModulusT0, poissonRatioT0 ) else: - self.logger( "Elastic moduli at initial time are absent." ) - return False + bulkModulusT0 = getArrayInObject( self.output, self.bulkModulusT0AttributeName, self.bulkModulusT0OnPoints ) # Compute Biot at initial time step. biotCoefficientT0: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.grainBulkModulus, bulkModulusT0 ) From 44766fd17e00d837ce2daa420da35b7b53d793af Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Wed, 8 Oct 2025 15:43:26 +0200 Subject: [PATCH 31/40] update import --- geos-posp/pyproject.toml | 1 + geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py | 1 - geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py | 1 - .../src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py | 1 - geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py | 1 - geos-posp/src/PVplugins/__init__.py | 2 +- 6 files changed, 2 insertions(+), 5 deletions(-) diff --git a/geos-posp/pyproject.toml b/geos-posp/pyproject.toml index 2b07d0de3..ddd3d0626 100644 --- a/geos-posp/pyproject.toml +++ b/geos-posp/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ "geos-geomechanics", "geos-utils", "geos-mesh", + "geos-processing", "vtk >= 9.3", "numpy >= 2.2", "pandas >= 2.2", diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py index f9954afd6..88892f9ae 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py @@ -11,7 +11,6 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -# TODO: Change the way of import file, it is not working with Paraview 5.13 dir_path = os.path.dirname( os.path.realpath( __file__ ) ) parent_dir_path = os.path.dirname( dir_path ) if parent_dir_path not in sys.path: diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py index 26ca8ea8e..f363e39bf 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py @@ -11,7 +11,6 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -# TODO: Change the way of import file, it is not working with Paraview 5.13 dir_path = os.path.dirname( os.path.realpath( __file__ ) ) parent_dir_path = os.path.dirname( dir_path ) if parent_dir_path not in sys.path: diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py index 8a69c4e16..928995a86 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py @@ -11,7 +11,6 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -# TODO: Change the way of import file, it is not working with Paraview 5.13 dir_path = os.path.dirname( os.path.realpath( __file__ ) ) parent_dir_path = os.path.dirname( dir_path ) if parent_dir_path not in sys.path: diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py index f8ce3dad8..8435c6a13 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py @@ -11,7 +11,6 @@ from vtkmodules.vtkCommonDataModel import ( vtkMultiBlockDataSet, ) -# TODO: Change the way of import file, it is not working with Paraview 5.13 dir_path = os.path.dirname( os.path.realpath( __file__ ) ) parent_dir_path = os.path.dirname( dir_path ) if parent_dir_path not in sys.path: diff --git a/geos-posp/src/PVplugins/__init__.py b/geos-posp/src/PVplugins/__init__.py index 6271e4ad0..37e7c3cf2 100644 --- a/geos-posp/src/PVplugins/__init__.py +++ b/geos-posp/src/PVplugins/__init__.py @@ -5,7 +5,7 @@ dir_path = os.path.dirname( os.path.realpath( __file__ ) ) python_root = '../../..' -python_modules = ( 'geos-posp', 'geos-utils', 'geos-geomechanics', 'geos-mesh' ) +python_modules = ( 'geos-posp', 'geos-utils', 'geos-geomechanics', 'geos-mesh', 'geos-processing' ) for m in python_modules: m_path = os.path.abspath( os.path.join( dir_path, python_root, m, 'src' ) ) From 35c35414c245c59b6de85c03410ec35e0ef1e3f1 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Wed, 8 Oct 2025 15:58:09 +0200 Subject: [PATCH 32/40] fix Install packages --- .github/workflows/python-package.yml | 19 +++++++++++-------- .github/workflows/typing-check.yml | 4 ++-- docs/conf.py | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 8ae0563e8..9f2cf543e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -1,4 +1,4 @@ -name: geosPythonPackages CI +name: geosPythonPackages CI on: pull_request # Cancels in-progress workflows for a PR when updated @@ -9,13 +9,13 @@ concurrency: jobs: # Checks if PR title follows conventional semantics - semantic_pull_request: + semantic_pull_request: permissions: - pull-requests: write # for amannn/action-semantic-pull-request to analyze PRs and + pull-requests: write # for amannn/action-semantic-pull-request to analyze PRs and statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR - contents: read + contents: read runs-on: ubuntu-latest - + steps: - name: Check if the PR name has conventional semantics if: github.event_name == 'pull_request' @@ -27,7 +27,7 @@ jobs: wip: true # Configure that a scope doesn't need to be provided. requireScope: false - + - name: Skip the check on main branch if: github.ref_name == 'main' run: | @@ -40,11 +40,12 @@ jobs: max-parallel: 3 matrix: python-version: ["3.10", "3.11", "3.12"] - package-name: + package-name: - geos-ats - geos-geomechanics - geos-mesh - geos-posp + - geos-processing - geos-timehistory - geos-trame - geos-utils @@ -59,6 +60,8 @@ jobs: dependencies: "geos-utils geos-geomechanics" - package-name: geos-posp dependencies: "geos-utils geos-mesh geos-geomechanics" + - package-name: geos-processing + dependencies: "geos-utils geos-mesh geos-geomechanics geos-posp" - package-name: pygeos-tools dependencies: "geos-utils geos-mesh" - package-name: geos-timehistory @@ -77,7 +80,7 @@ jobs: python -m pip install --upgrade pip python -m pip install pytest yapf toml - DEPS="${{ matrix.dependencies || '' }}" + DEPS="${{ matrix.dependencies || '' }}" if [ -n "$DEPS" ]; then echo "Installing additional dependencies: $DEPS" diff --git a/.github/workflows/typing-check.yml b/.github/workflows/typing-check.yml index 4b53634b4..0a00276dc 100644 --- a/.github/workflows/typing-check.yml +++ b/.github/workflows/typing-check.yml @@ -1,5 +1,5 @@ name: "Pull Request Typing Check" -on: +on: - pull_request # Cancels in-progress workflows for a PR when updated @@ -16,7 +16,7 @@ jobs: max-parallel: 3 matrix: # add packages to check typing - package-name: ["geos-geomechanics", "geos-posp", "geos-timehistory", "geos-utils", "geos-trame", "geos-xml-tools", "hdf5-wrapper"] + package-name: ["geos-geomechanics", "geos-posp", "geos-processing", "geos-timehistory", "geos-utils", "geos-trame", "geos-xml-tools", "hdf5-wrapper"] steps: - uses: actions/checkout@v4 diff --git a/docs/conf.py b/docs/conf.py index 7d925de4b..7f9197b07 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,7 +17,7 @@ # Add python modules to be documented python_root = '..' -python_modules = ( 'geos-ats', 'geos-geomechanics', 'geos-mesh', 'geos-posp', 'geos-pv', 'geos-timehistory', +python_modules = ( 'geos-ats', 'geos-geomechanics', 'geos-mesh', 'geos-posp', 'geos-processing', 'geos-pv', 'geos-timehistory', 'geos-utils', 'geos-xml-tools', 'geos-xml-viewer', 'hdf5-wrapper', 'pygeos-tools' ) @@ -45,7 +45,7 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.napoleon', 'sphinx_design', 'sphinx_rtd_theme', 'sphinxarg.ext', 'sphinxcontrib.programoutput', + 'sphinx.ext.napoleon', 'sphinx_design', 'sphinx_rtd_theme', 'sphinxarg.ext', 'sphinxcontrib.programoutput', 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.mathjax', 'sphinx.ext.todo', 'sphinx.ext.viewcode' ] From 7da51689c01786d084c902eebe06b9e9a291af78 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Wed, 8 Oct 2025 17:51:41 +0200 Subject: [PATCH 33/40] fix ci --- .github/workflows/python-package.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 9f2cf543e..33c4991ab 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -58,10 +58,10 @@ jobs: dependencies: "geos-utils" - package-name: geos-mesh dependencies: "geos-utils geos-geomechanics" - - package-name: geos-posp - dependencies: "geos-utils geos-mesh geos-geomechanics" - package-name: geos-processing - dependencies: "geos-utils geos-mesh geos-geomechanics geos-posp" + dependencies: "geos-utils geos-mesh geos-geomechanics" + - package-name: geos-posp + dependencies: "geos-utils geos-mesh geos-geomechanics, geos-processing" - package-name: pygeos-tools dependencies: "geos-utils geos-mesh" - package-name: geos-timehistory From 795d6d99469893fa198db59f896d6d1fa1339438 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Wed, 8 Oct 2025 17:59:26 +0200 Subject: [PATCH 34/40] fix geos-post build --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 33c4991ab..526366e5f 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -61,7 +61,7 @@ jobs: - package-name: geos-processing dependencies: "geos-utils geos-mesh geos-geomechanics" - package-name: geos-posp - dependencies: "geos-utils geos-mesh geos-geomechanics, geos-processing" + dependencies: "geos-utils geos-mesh geos-geomechanics geos-processing" - package-name: pygeos-tools dependencies: "geos-utils geos-mesh" - package-name: geos-timehistory From 1902c772e22f05ea5df39dd5e647f72e03124104 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 14 Oct 2025 13:08:51 +0200 Subject: [PATCH 35/40] Apply Jacques suggestion with dataclass --- .../PVplugins/PVGeomechanicsWorkflowVolume.py | 9 +- .../PVGeomechanicsWorkflowVolumeSurface.py | 8 +- ...PVGeomechanicsWorkflowVolumeSurfaceWell.py | 8 +- .../PVGeomechanicsWorkflowVolumeWell.py | 8 +- .../post_processing/GeomechanicsCalculator.py | 1300 ++++++++++------- .../pv/plugins/PVGeomechanicsCalculator.py | 8 +- 6 files changed, 792 insertions(+), 549 deletions(-) diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py index 88892f9ae..2b2aa1e29 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py @@ -334,7 +334,6 @@ def RequestData( self.doExtractAndMerge() # 2. compute Geomechanical outputs in volume mesh a = self.computeAdditionalOutputsVolume() - print( a ) except AssertionError as e: mess: str = "Geomechanics workflow failed due to:" @@ -378,10 +377,10 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: speHandler=True ) if not filter.logger.hasHandlers(): filter.setLoggerHandler( VTKHandler() ) - filter.setGrainBulkModulus( self.getGrainBulkModulus() ) - filter.setSpecificDensity( self.getSpecificDensity() ) - filter.setRockCohesion( self.getRockCohesion() ) - filter.setFrictionAngle( self.getFrictionAngle() ) + filter.physicalConstants.grainBulkModulus = self.grainBulkModulus + filter.physicalConstants.specificDensity = self.specificDensity + filter.physicalConstants.rockCohesion = self.rockCohesion + filter.physicalConstants.frictionAngle = self.frictionAngle filter.applyFilter() self.m_volumeMesh.ShallowCopy( filter.getOutput() ) self.m_volumeMesh.Modified() diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py index f363e39bf..8b5596883 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py @@ -378,10 +378,10 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: speHandler=True ) if not filter.logger.hasHandlers(): filter.setLoggerHandler( VTKHandler() ) - filter.setGrainBulkModulus( self.getGrainBulkModulus() ) - filter.setSpecificDensity( self.getSpecificDensity() ) - filter.setRockCohesion( self.getRockCohesion() ) - filter.setFrictionAngle( self.getFrictionAngle() ) + filter.physicalConstants.grainBulkModulus = self.grainBulkModulus + filter.physicalConstants.specificDensity = self.specificDensity + filter.physicalConstants.rockCohesion = self.rockCohesion + filter.physicalConstants.frictionAngle = self.frictionAngle filter.applyFilter() self.m_volumeMesh.ShallowCopy( filter.getOutput() ) self.m_volumeMesh.Modified() diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py index 928995a86..715de74c0 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py @@ -387,10 +387,10 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: speHandler=True ) if not filter.logger.hasHandlers(): filter.setLoggerHandler( VTKHandler() ) - filter.setGrainBulkModulus( self.getGrainBulkModulus() ) - filter.setSpecificDensity( self.getSpecificDensity() ) - filter.setRockCohesion( self.getRockCohesion() ) - filter.setFrictionAngle( self.getFrictionAngle() ) + filter.physicalConstants.grainBulkModulus = self.grainBulkModulus + filter.physicalConstants.specificDensity = self.specificDensity + filter.physicalConstants.rockCohesion = self.rockCohesion + filter.physicalConstants.frictionAngle = self.frictionAngle filter.applyFilter() self.m_volumeMesh.ShallowCopy( filter.getOutput() ) self.m_volumeMesh.Modified() diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py index 8435c6a13..0d5829ddf 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeWell.py @@ -392,10 +392,10 @@ def computeAdditionalOutputsVolume( self: Self ) -> bool: speHandler=True ) if not filter.logger.hasHandlers(): filter.setLoggerHandler( VTKHandler() ) - filter.setGrainBulkModulus( self.getGrainBulkModulus() ) - filter.setSpecificDensity( self.getSpecificDensity() ) - filter.setRockCohesion( self.getRockCohesion() ) - filter.setFrictionAngle( self.getFrictionAngle() ) + filter.physicalConstants.grainBulkModulus = self.grainBulkModulus + filter.physicalConstants.specificDensity = self.specificDensity + filter.physicalConstants.rockCohesion = self.rockCohesion + filter.physicalConstants.frictionAngle = self.frictionAngle filter.applyFilter() self.m_volumeMesh.ShallowCopy( filter.getOutput() ) self.m_volumeMesh.Modified() diff --git a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py index 9a96f005f..c601c5296 100644 --- a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py +++ b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py @@ -2,8 +2,9 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay, Romain Baville # ruff: noqa: E402 # disable Module level import not at top of file -from typing import Union, Dict, Any +from typing import Union from typing_extensions import Self +from dataclasses import dataclass, field import logging import numpy as np @@ -51,7 +52,6 @@ - The pressure named "pressure" The basic geomechanics outputs are: - - Elastic modulus (young modulus and poisson ratio or bulk modulus and shear modulus) - Biot coefficient - Compressibility, oedometric compressibility and real compressibility coefficient - Specific gravity @@ -120,8 +120,376 @@ loggerTitle: str = "Geomechanical Calculator Filter" +# Elastic Moduli: +BULK_MODULUS: AttributeEnum = GeosMeshOutputsEnum.BULK_MODULUS +SHEAR_MODULUS: AttributeEnum = GeosMeshOutputsEnum.SHEAR_MODULUS +YOUNG_MODULUS: AttributeEnum = PostProcessingOutputsEnum.YOUNG_MODULUS +POISSON_RATIO: AttributeEnum = PostProcessingOutputsEnum.POISSON_RATIO +BULK_MODULUS_T0: AttributeEnum = PostProcessingOutputsEnum.BULK_MODULUS_INITIAL +YOUNG_MODULUS_T0: AttributeEnum = PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL +POISSON_RATIO_T0: AttributeEnum = PostProcessingOutputsEnum.POISSON_RATIO_INITIAL + +# Mandatory attributes: +POROSITY: AttributeEnum = GeosMeshOutputsEnum.POROSITY +POROSITY_T0: AttributeEnum = GeosMeshOutputsEnum.POROSITY_INI +PRESSURE: AttributeEnum = GeosMeshOutputsEnum.PRESSURE +DELTA_PRESSURE: AttributeEnum = GeosMeshOutputsEnum.DELTA_PRESSURE +DENSITY: AttributeEnum = GeosMeshOutputsEnum.ROCK_DENSITY +STRESS_EFFECTIVE: AttributeEnum = GeosMeshOutputsEnum.STRESS_EFFECTIVE +STRESS_EFFECTIVE_T0: AttributeEnum = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL + +# Basic outputs: +BIOT_COEFFICIENT: AttributeEnum = PostProcessingOutputsEnum.BIOT_COEFFICIENT +COMPRESSIBILITY: AttributeEnum = PostProcessingOutputsEnum.COMPRESSIBILITY +COMPRESSIBILITY_OED: AttributeEnum = PostProcessingOutputsEnum.COMPRESSIBILITY_OED +COMPRESSIBILITY_REAL: AttributeEnum = PostProcessingOutputsEnum.COMPRESSIBILITY_REAL +SPECIFIC_GRAVITY: AttributeEnum = PostProcessingOutputsEnum.SPECIFIC_GRAVITY +STRESS_EFFECTIVE_RATIO_REAL: AttributeEnum = PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_REAL +STRESS_TOTAL: AttributeEnum = PostProcessingOutputsEnum.STRESS_TOTAL +STRESS_TOTAL_T0: AttributeEnum = PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL +STRESS_TOTAL_RATIO_REAL: AttributeEnum = PostProcessingOutputsEnum.STRESS_TOTAL_RATIO_REAL +LITHOSTATIC_STRESS: AttributeEnum = PostProcessingOutputsEnum.LITHOSTATIC_STRESS +STRAIN_ELASTIC: AttributeEnum = PostProcessingOutputsEnum.STRAIN_ELASTIC +STRESS_TOTAL_DELTA: AttributeEnum = PostProcessingOutputsEnum.STRESS_TOTAL_DELTA +RSP_REAL: AttributeEnum = PostProcessingOutputsEnum.RSP_REAL +RSP_OED: AttributeEnum = PostProcessingOutputsEnum.RSP_OED +STRESS_EFFECTIVE_RATIO_OED: AttributeEnum = PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_OED + +# Advanced outputs: +CRITICAL_TOTAL_STRESS_RATIO: AttributeEnum = PostProcessingOutputsEnum.CRITICAL_TOTAL_STRESS_RATIO +TOTAL_STRESS_RATIO_THRESHOLD: AttributeEnum = PostProcessingOutputsEnum.TOTAL_STRESS_RATIO_THRESHOLD +CRITICAL_PORE_PRESSURE: AttributeEnum = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE +CRITICAL_PORE_PRESSURE_THRESHOLD: AttributeEnum = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD + + +class GeomechanicsCalculator: + + @dataclass + class PhysicalConstants: + ## Basic outputs + _grainBulkModulus: float = DEFAULT_GRAIN_BULK_MODULUS + _specificDensity: float = WATER_DENSITY + ## Advanced outputs + _rockCohesion: float = DEFAULT_ROCK_COHESION + _frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD + + @property + def grainBulkModulus( self: Self ) -> float: + return self._grainBulkModulus + + @grainBulkModulus.setter + def grainBulkModulus( self: Self, value: float ) -> None: + self._grainBulkModulus = value + + @property + def specificDensity( self: Self ) -> float: + return self._specificDensity + + @specificDensity.setter + def specificDensity( self: Self, value: float ) -> None: + self._specificDensity = value + + @property + def rockCohesion( self: Self ) -> float: + return self._rockCohesion + + @rockCohesion.setter + def rockCohesion( self: Self, value: float ) -> None: + self._rockCohesion = value + + @property + def frictionAngle( self: Self ) -> float: + return self._frictionAngle + + @frictionAngle.setter + def frictionAngle( self: Self, value: float ) -> None: + self._frictionAngle = value + + physicalConstants: PhysicalConstants = PhysicalConstants() + + @dataclass + class MandatoryAttributes: + _dictMandatoryAttribute: dict[ AttributeEnum, npt.NDArray[ np.float64 ] ] = field( default_factory=dict ) + + @property + def dictMandatoryAttribute( self: Self ) -> dict[ AttributeEnum, npt.NDArray[ np.float64 ] ]: + return self._dictMandatoryAttribute + + @dictMandatoryAttribute.setter + def dictMandatoryAttribute( self: Self, attributeValue: tuple[ AttributeEnum, npt.NDArray[ np.float64 ] ] ) -> None: + self._dictMandatoryAttribute[ attributeValue[ 0 ] ] = attributeValue[ 1 ] + + @property + def porosity( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictMandatoryAttribute[ POROSITY ] + + @property + def porosityInitial( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictMandatoryAttribute[ POROSITY_T0 ] + + @property + def pressure( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictMandatoryAttribute[ PRESSURE ] + + @property + def deltaPressure( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictMandatoryAttribute[ DELTA_PRESSURE ] + + @property + def density( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictMandatoryAttribute[ DENSITY ] + + @property + def effectiveStress( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictMandatoryAttribute[ STRESS_EFFECTIVE ] + + @property + def effectiveStressT0( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictMandatoryAttribute[ STRESS_EFFECTIVE_T0 ] + + _mandatoryAttributes: MandatoryAttributes = MandatoryAttributes() + + @dataclass + class ElasticModuli: + _dictElasticModuli: dict[ AttributeEnum, npt.NDArray[ np.float64 ] ] = field( default_factory=dict) + + @property + def dictElasticModuli( self: Self ) -> dict[ AttributeEnum, npt.NDArray[ np.float64 ] ]: + return self._dictElasticModuli + + @dictElasticModuli.setter + def dictElasticModuli( self: Self, value: tuple[ AttributeEnum, npt.NDArray[ np.float64 ] ] ) -> None: + self._dictElasticModuli[ value[ 0 ] ] = value[ 1 ] + + @property + def bulkModulus( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictElasticModuli[ BULK_MODULUS ] + + @bulkModulus.setter + def bulkModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictElasticModuli[ BULK_MODULUS ] = value + + @property + def shearModulus( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictElasticModuli[ SHEAR_MODULUS ] + + @shearModulus.setter + def shearModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictElasticModuli[ SHEAR_MODULUS ] = value + + @property + def youngModulus( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictElasticModuli[ YOUNG_MODULUS ] + + @youngModulus.setter + def youngModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictElasticModuli[ YOUNG_MODULUS ] = value + + @property + def poissonRatio( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictElasticModuli[ POISSON_RATIO ] + + @poissonRatio.setter + def poissonRatio( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictElasticModuli[ POISSON_RATIO ] = value + + @property + def bulkModulusT0( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictElasticModuli[ BULK_MODULUS_T0 ] + + @bulkModulusT0.setter + def bulkModulusT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictElasticModuli[ BULK_MODULUS_T0 ] = value + + @property + def youngModulusT0( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictElasticModuli[ YOUNG_MODULUS_T0 ] + + @youngModulusT0.setter + def youngModulusT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictElasticModuli[ YOUNG_MODULUS_T0 ] = value + + @property + def poissonRatioT0( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictElasticModuli[ POISSON_RATIO_T0 ] + + @poissonRatioT0.setter + def poissonRatioT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictElasticModuli[ POISSON_RATIO_T0 ] = value + + _elasticModuli: ElasticModuli = ElasticModuli() + + @dataclass + class BasicOutput: + _dictBasicOutput: dict[ AttributeEnum, npt.NDArray[ np.float64 ] ] = field( default_factory=dict ) + + @property + def dictBasicOutput( self: Self ) -> dict[ AttributeEnum, npt.NDArray[ np.float64 ] ]: + return self._dictBasicOutput + + @property + def biotCoefficient( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ BIOT_COEFFICIENT ] + + @biotCoefficient.setter + def biotCoefficient( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ BIOT_COEFFICIENT ] = value + + @property + def compressibility( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ COMPRESSIBILITY ] + + @compressibility.setter + def compressibility( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ COMPRESSIBILITY ] = value + + @property + def compressibilityOed( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ COMPRESSIBILITY_OED ] + + @compressibilityOed.setter + def compressibilityOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ COMPRESSIBILITY_OED ] = value + + @property + def compressibilityReal( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ COMPRESSIBILITY_REAL ] + + @compressibilityReal.setter + def compressibilityReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ COMPRESSIBILITY_REAL ] = value + + @property + def specificGravity( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ SPECIFIC_GRAVITY ] + + @specificGravity.setter + def specificGravity( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ SPECIFIC_GRAVITY ] = value + + @property + def effectiveStressRatioReal( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ STRESS_EFFECTIVE_RATIO_REAL ] + + @effectiveStressRatioReal.setter + def effectiveStressRatioReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ STRESS_EFFECTIVE_RATIO_REAL ] = value + + @property + def totalStress( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ STRESS_TOTAL ] + + @totalStress.setter + def totalStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ STRESS_TOTAL ] = value + + @property + def totalStressT0( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ STRESS_TOTAL_T0 ] + + @totalStressT0.setter + def totalStressT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ STRESS_TOTAL_T0 ] = value + + @property + def totalStressRatioReal( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ STRESS_TOTAL_RATIO_REAL ] + + @totalStressRatioReal.setter + def totalStressRatioReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ STRESS_TOTAL_RATIO_REAL ] = value + + @property + def lithostaticStress( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ LITHOSTATIC_STRESS ] + + @lithostaticStress.setter + def lithostaticStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ LITHOSTATIC_STRESS ] = value + + @property + def elasticStrain( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ STRAIN_ELASTIC ] + + @elasticStrain.setter + def elasticStrain( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ STRAIN_ELASTIC ] = value + + @property + def deltaTotalStress( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ STRESS_TOTAL_DELTA ] + + @deltaTotalStress.setter + def deltaTotalStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ STRESS_TOTAL_DELTA ] = value + + @property + def rspReal( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ RSP_REAL ] + + @rspReal.setter + def rspReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ RSP_REAL ] = value + + @property + def rspOed( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ RSP_OED ] + + @rspOed.setter + def rspOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ RSP_OED ] = value + + @property + def effectiveStressRatioOed( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictBasicOutput[ STRESS_EFFECTIVE_RATIO_OED ] + + @effectiveStressRatioOed.setter + def effectiveStressRatioOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictBasicOutput[ STRESS_EFFECTIVE_RATIO_OED ] = value + + _basicOutput: BasicOutput = BasicOutput() + + @dataclass + class AdvancedOutput: + _dictAdvancedOutput: dict[ AttributeEnum, npt.NDArray[ np.float64 ] ] = field( default_factory=dict ) + + @property + def dictAdvancedOutput( self: Self ) -> dict[ AttributeEnum, npt.NDArray[ np.float64 ] ]: + return self._dictAdvancedOutput + + @property + def criticalTotalStressRatio( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictAdvancedOutput[ CRITICAL_TOTAL_STRESS_RATIO ] + + @criticalTotalStressRatio.setter + def criticalTotalStressRatio( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictAdvancedOutput[ CRITICAL_TOTAL_STRESS_RATIO ] = value + + @property + def stressRatioThreshold( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictAdvancedOutput[ TOTAL_STRESS_RATIO_THRESHOLD ] + + @stressRatioThreshold.setter + def stressRatioThreshold( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictAdvancedOutput[ TOTAL_STRESS_RATIO_THRESHOLD ] = value + + @property + def criticalPorePressure( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictAdvancedOutput[ CRITICAL_PORE_PRESSURE ] + + @criticalPorePressure.setter + def criticalPorePressure( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictAdvancedOutput[ CRITICAL_PORE_PRESSURE ] = value + + @property + def criticalPorePressureIndex( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._dictAdvancedOutput[ CRITICAL_PORE_PRESSURE_THRESHOLD ] -class GeomechanicsCalculator(): + @criticalPorePressureIndex.setter + def criticalPorePressureIndex( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: + self._dictAdvancedOutput[ CRITICAL_PORE_PRESSURE_THRESHOLD ] = value + + _advancedOutput: AdvancedOutput = AdvancedOutput() def __init__( self: Self, @@ -138,22 +506,54 @@ def __init__( speHandler (bool, optional): True to use a specific handler, False to use the internal handler. Defaults to False. """ - self.output: Union[ vtkPointSet, vtkUnstructuredGrid ] - if mesh.IsA( "vtkUnstructuredGrid" ): - self.output = vtkUnstructuredGrid() - elif mesh.IsA( "vtkPointSet" ): - self.output = vtkPointSet() + self.output: Union[ vtkPointSet, vtkUnstructuredGrid ] = mesh.NewInstance() self.output.DeepCopy( mesh ) self.doComputeAdvancedOutputs: bool = computeAdvancedOutputs - # Defaults physical constants - ## Basic outputs - self.grainBulkModulus: float = DEFAULT_GRAIN_BULK_MODULUS - self.specificDensity: float = WATER_DENSITY - ## Advanced outputs - self.rockCohesion: float = DEFAULT_ROCK_COHESION - self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD + self._mandatoryAttributes._dictMandatoryAttribute = { + POROSITY: np.array( [] ), + POROSITY_T0: np.array( [] ), + PRESSURE: np.array( [] ), + DELTA_PRESSURE: np.array( [] ), + DENSITY: np.array( [] ), + STRESS_EFFECTIVE: np.array( [] ), + STRESS_EFFECTIVE_T0: np.array( [] ), + } + self._elasticModuli._dictElasticModuli = { + BULK_MODULUS: np.array( [] ), + SHEAR_MODULUS: np.array( [] ), + YOUNG_MODULUS: np.array( [] ), + POISSON_RATIO: np.array( [] ), + BULK_MODULUS_T0: np.array( [] ), + YOUNG_MODULUS_T0: np.array( [] ), + POISSON_RATIO_T0: np.array( [] ), + } + self._basicOutput._dictBasicOutput = { + BIOT_COEFFICIENT: np.array( [] ), + COMPRESSIBILITY: np.array( [] ), + COMPRESSIBILITY_OED: np.array( [] ), + COMPRESSIBILITY_REAL: np.array( [] ), + SPECIFIC_GRAVITY: np.array( [] ), + STRESS_EFFECTIVE_RATIO_REAL: np.array( [] ), + STRESS_TOTAL: np.array( [] ), + STRESS_TOTAL_T0: np.array( [] ), + STRESS_TOTAL_RATIO_REAL: np.array( [] ), + # LITHOSTATIC_STRESS: np.array( [] ), + STRAIN_ELASTIC: np.array( [] ), + STRESS_TOTAL_DELTA: np.array( [] ), + RSP_REAL: np.array( [] ), + RSP_OED: np.array( [] ), + STRESS_EFFECTIVE_RATIO_OED: np.array( [] ), + } + self._advancedOutput._dictAdvancedOutput = { + CRITICAL_TOTAL_STRESS_RATIO: np.array( [] ), + TOTAL_STRESS_RATIO_THRESHOLD: np.array( [] ), + CRITICAL_PORE_PRESSURE: np.array( [] ), + CRITICAL_PORE_PRESSURE_THRESHOLD: np.array( [] ), + } + + self._outputPresent: list[ str ] = [] # Logger. self.logger: Logger @@ -169,8 +569,7 @@ def applyFilter( self: Self ) -> bool: Returns: Boolean (bool): True if calculation successfully ended, False otherwise. """ - if not self.checkMandatoryAttributes(): - self.logger.error( "The filter failed." ) + if not self._checkMandatoryAttributes(): return False if not self.computeBasicOutputs(): @@ -204,38 +603,6 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: "The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization." ) - def setGrainBulkModulus( self: Self, grainBulkModulus: float ) -> None: - """Set the grain bulk modulus. - - Args: - grainBulkModulus (float): Grain bulk modulus. - """ - self.grainBulkModulus = grainBulkModulus - - def setSpecificDensity( self: Self, specificDensity: float ) -> None: - """Set the specific density. - - Args: - specificDensity (float): Specific density. - """ - self.specificDensity = specificDensity - - def setRockCohesion( self: Self, rockCohesion: float ) -> None: - """Set the rock cohesion. - - Args: - rockCohesion (float): Rock cohesion. - """ - self.rockCohesion = rockCohesion - - def setFrictionAngle( self: Self, frictionAngle: float ) -> None: - """Set the friction angle. - - Args: - frictionAngle (float): Friction angle (rad) - """ - self.frictionAngle = frictionAngle - def getOutputType( self: Self ) -> str: """Get output object type. @@ -244,7 +611,7 @@ def getOutputType( self: Self ) -> str: """ return self.output.GetClassName() - def checkMandatoryAttributes( self: Self ) -> bool: + def _checkMandatoryAttributes( self: Self ) -> bool: """Check that mandatory attributes are present in the mesh. The mesh must contains: @@ -252,114 +619,74 @@ def checkMandatoryAttributes( self: Self ) -> bool: - The initial Young modulus and Poisson's ratio named "youngModulusInitial" and "poissonRatioInitial" or the initial bulk modulus named "bulkModulusInitial" - The porosity named "porosity" - The initial porosity named "porosityInitial" + - The pressure named "pressure" - The delta of pressure named "deltaPressure" - The density named "density" - The effective stress named "stressEffective" - The initial effective stress named "stressEffectiveInitial" - - The pressure named "pressure" Returns: bool: True if all needed attributes are present, False otherwise """ - # Check the presence of the elastic moduli at the current time. - self.youngModulusAttributeName: str = PostProcessingOutputsEnum.YOUNG_MODULUS.attributeName - self.youngModulusOnPoints: bool = PostProcessingOutputsEnum.YOUNG_MODULUS.isOnPoints - youngModulusOnMesh: bool = isAttributeInObject( self.output, self.youngModulusAttributeName, - self.youngModulusOnPoints ) - - self.poissonRatioAttributeName: str = PostProcessingOutputsEnum.POISSON_RATIO.attributeName - self.poissonRatioOnPoints: bool = PostProcessingOutputsEnum.POISSON_RATIO.isOnPoints - poissonRatioOnMesh: bool = isAttributeInObject( self.output, self.poissonRatioAttributeName, - self.poissonRatioOnPoints ) - - self.bulkModulusAttributeName: str = GeosMeshOutputsEnum.BULK_MODULUS.attributeName - self.bulkModulusOnPoints: bool = GeosMeshOutputsEnum.BULK_MODULUS.isOnPoints - bulkModulusOnMesh: bool = isAttributeInObject( self.output, self.bulkModulusAttributeName, - self.bulkModulusOnPoints ) - - self.shearModulusAttributeName: str = GeosMeshOutputsEnum.SHEAR_MODULUS.attributeName - self.shearModulusOnPoints: bool = GeosMeshOutputsEnum.SHEAR_MODULUS.isOnPoints - shearModulusOnMesh: bool = isAttributeInObject( self.output, self.shearModulusAttributeName, - self.shearModulusOnPoints ) + for elasticModulus in self._elasticModuli.dictElasticModuli: + elasticModulusName: str = elasticModulus.attributeName + elasticModulusOnPoints: bool = elasticModulus.isOnPoints + if isAttributeInObject( self.output, elasticModulusName, elasticModulusOnPoints ): + self._elasticModuli._dictElasticModuli = ( elasticModulus, getArrayInObject( self.output, elasticModulusName, elasticModulusOnPoints ) ) + # Check the presence of the elastic moduli at the current time. self.computeYoungPoisson: bool - if not youngModulusOnMesh and not poissonRatioOnMesh: - if bulkModulusOnMesh and shearModulusOnMesh: + if self._elasticModuli.youngModulus.size == 0 and self._elasticModuli.poissonRatio.size == 0: + if self._elasticModuli.bulkModulus.size != 0 and self._elasticModuli.shearModulus.size != 0: + self._elasticModuli.youngModulus = fcts.youngModulus( self._elasticModuli.bulkModulus, + self._elasticModuli.shearModulus ) + self._elasticModuli.poissonRatio = fcts.poissonRatio( self._elasticModuli.bulkModulus, + self._elasticModuli.shearModulus ) self.computeYoungPoisson = True else: self.logger.error( - f"{ self.bulkModulusAttributeName } or { self.shearModulusAttributeName } are missing to compute geomechanical outputs." + f"{ BULK_MODULUS.attributeName } or { SHEAR_MODULUS.attributeName } are missing to compute geomechanical outputs." ) return False - elif not bulkModulusOnMesh and not shearModulusOnMesh: - if youngModulusOnMesh and poissonRatioOnMesh: + elif self._elasticModuli.bulkModulus.size == 0 and self._elasticModuli.shearModulus.size == 0: + if self._elasticModuli.youngModulus.size != 0 and self._elasticModuli.poissonRatio.size != 0: + self._elasticModuli.bulkModulus = fcts.bulkModulus( self._elasticModuli.youngModulus, + self._elasticModuli.poissonRatio ) + self._elasticModuli.shearModulus = fcts.shearModulus( self._elasticModuli.youngModulus, + self._elasticModuli.poissonRatio ) self.computeYoungPoisson = False else: self.logger.error( - f"{ self.youngModulusAttributeName } or { self.poissonRatioAttributeName } are missing to compute geomechanical outputs." + f"{ YOUNG_MODULUS.attributeName } or { POISSON_RATIO.attributeName } are missing to compute geomechanical outputs." ) return False else: self.logger.error( - f"{ self.bulkModulusAttributeName } and { self.shearModulusAttributeName } or { self.youngModulusAttributeName } and { self.poissonRatioAttributeName } are mandatory to compute geomechanical outputs." + f"{ BULK_MODULUS.attributeName } and { SHEAR_MODULUS.attributeName } or { YOUNG_MODULUS.attributeName } and { POISSON_RATIO.attributeName } are mandatory to compute geomechanical outputs." ) return False # Check the presence of the elastic moduli at the initial time. - self.bulkModulusT0AttributeName: str = PostProcessingOutputsEnum.BULK_MODULUS_INITIAL.attributeName - self.bulkModulusT0OnPoints: bool = PostProcessingOutputsEnum.BULK_MODULUS_INITIAL.isOnPoints - bulkModulusT0OnMesh: bool = isAttributeInObject( self.output, self.bulkModulusT0AttributeName, - self.bulkModulusT0OnPoints ) - - self.youngModulusT0AttributeName: str = PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.attributeName - self.youngModulusT0OnPoints: bool = PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.isOnPoints - youngModulusT0OnMesh: bool = isAttributeInObject( self.output, self.youngModulusT0AttributeName, - self.youngModulusT0OnPoints ) - - self.poissonRatioT0AttributeName: str = PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.attributeName - self.poissonRatioT0OnPoints: bool = PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.isOnPoints - poissonRatioT0OnMesh: bool = isAttributeInObject( self.output, self.poissonRatioT0AttributeName, - self.poissonRatioT0OnPoints ) - - self.computeInitialBulkModulus: bool - if bulkModulusT0OnMesh: - self.computeInitialBulkModulus = False - elif youngModulusT0OnMesh and poissonRatioT0OnMesh: - self.computeInitialBulkModulus = True - else: - self.logger.error( - f"{ self.bulkModulusT0AttributeName } or { self.youngModulusT0AttributeName } and { self.poissonRatioT0AttributeName } are mandatory to compute geomechanical outputs." - ) - return False + if self._elasticModuli.bulkModulusT0.size == 0: + if self._elasticModuli.youngModulusT0.size != 0 and self._elasticModuli.poissonRatioT0.size != 0: + self._elasticModuli.bulkModulusT0 = fcts.bulkModulus( self._elasticModuli.youngModulusT0, + self._elasticModuli.poissonRatioT0 ) + else: + self.logger.error( + f"{ BULK_MODULUS_T0.attributeName } or { YOUNG_MODULUS_T0.attributeName } and { POISSON_RATIO_T0.attributeName } are mandatory to compute geomechanical outputs." + ) + return False # Check the presence of the other mandatory attributes - dictMandatoryAttribute: Dict[ Any, Any ] = { - GeosMeshOutputsEnum.POROSITY: None, - GeosMeshOutputsEnum.POROSITY_INI: None, - GeosMeshOutputsEnum.DELTA_PRESSURE: None, - GeosMeshOutputsEnum.ROCK_DENSITY: None, - GeosMeshOutputsEnum.STRESS_EFFECTIVE: None, - PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL: None, - GeosMeshOutputsEnum.PRESSURE: None, - } - for mandatoryAttribute in dictMandatoryAttribute: - attributeName: str = mandatoryAttribute.attributeName - onPoints: bool = mandatoryAttribute.isOnPoints - if not isAttributeInObject( self.output, attributeName, onPoints ): + for mandatoryAttribute in self._mandatoryAttributes.dictMandatoryAttribute: + mandatoryAttributeName: str = mandatoryAttribute.attributeName + mandatoryAttributeOnPoints: bool = mandatoryAttribute.isOnPoints + if not isAttributeInObject( self.output, mandatoryAttributeName, mandatoryAttributeOnPoints ): self.logger.error( - f"The mandatory attribute { attributeName } is missing to compute geomechanical outputs." ) + f"The mandatory attribute { mandatoryAttributeName } is missing to compute geomechanical outputs." ) return False else: - dictMandatoryAttribute[ mandatoryAttribute ] = getArrayInObject( self.output, attributeName, onPoints ) - - self.porosity: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.POROSITY ] - self.porosityInitial: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.POROSITY_INI ] - self.deltaPressure: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.DELTA_PRESSURE ] - self.density: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.ROCK_DENSITY ] - self.effectiveStress: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.STRESS_EFFECTIVE ] - self.effectiveStressT0: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ - PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL ] - self.pressure: npt.NDArray[ np.float64 ] = dictMandatoryAttribute[ GeosMeshOutputsEnum.PRESSURE ] + self._mandatoryAttributes.dictMandatoryAttribute = ( mandatoryAttribute, getArrayInObject( self.output, mandatoryAttributeName, mandatoryAttributeOnPoints ) ) return True @@ -369,22 +696,18 @@ def computeBasicOutputs( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - if not self.computeElasticModulus(): - self.logger.error( "Elastic modulus computation failed." ) - return False - - if not self.computeBiotCoefficient(): + if not self._computeBiotCoefficient(): self.logger.error( "Biot coefficient computation failed." ) return False - if not self.computeCompressibilityCoefficient(): + if not self._computeCompressibilityCoefficient(): return False - if not self.computeRealEffectiveStressRatio(): + if not self._computeRealEffectiveStressRatio(): self.logger.error( "Effective stress ratio computation failed." ) return False - if not self.computeSpecificGravity(): + if not self._computeSpecificGravity(): self.logger.error( "Specific gravity computation failed." ) return False @@ -393,25 +716,34 @@ def computeBasicOutputs( self: Self ) -> bool: # self.logger.error( "Lithostatic stress computation failed." ) # return False - if not self.computeTotalStresses(): + if not self._computeTotalStresses(): return False - if not self.computeElasticStrain(): + if not self._computeElasticStrain(): self.logger.error( "Elastic strain computation failed." ) return False - # oedometric DRSP (effective stress ratio in oedometric conditions) - if not self.computeEffectiveStressRatioOed(): + if not self._computeEffectiveStressRatioOed(): self.logger.error( "Effective stress ration in oedometric condition computation failed." ) return False - if not self.computeReservoirStressPathOed(): + if not self._computeReservoirStressPathOed(): self.logger.error( "Reservoir stress path in oedometric condition computation failed." ) return False - if not self.computeReservoirStressPathReal(): + if not self._computeReservoirStressPathReal(): return False + # Create an attribute on the mesh for each basic outputs + for basicOutput, array in self._basicOutput.dictBasicOutput.items(): + # Basic outputs with multiple components: + if basicOutput.attributeName in [ STRESS_TOTAL.attributeName, STRESS_TOTAL_T0.attributeName, STRESS_TOTAL_DELTA.attributeName, STRAIN_ELASTIC.attributeName, RSP_REAL.attributeName ]: + if not createAttribute( self.output, array, basicOutput.attributeName, componentNames=ComponentNameEnum.XYZ.value, onPoints=basicOutput.isOnPoints, logger=self.logger ): + return False + # Other basic outputs: + elif basicOutput.attributeName not in self._outputPresent and not createAttribute( self.output, array, basicOutput.attributeName, onPoints=basicOutput.isOnPoints, logger=self.logger ): + return False + self.logger.info( "All geomechanical basic outputs were successfully computed." ) return True @@ -421,133 +753,39 @@ def computeAdvancedOutputs( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - if not self.computeCriticalTotalStressRatio(): + if not self._computeCriticalTotalStressRatio(): return False - if not self.computeCriticalPorePressure(): + if not self._computeCriticalPorePressure(): return False - mess: str = ( "All geomechanical advanced outputs were successfully computed." ) - self.logger.info( mess ) - return True - - def computeElasticModulus( self: Self ) -> bool: - """Compute elastic moduli. - - Young modulus and the poisson ratio computed from shear and bulk moduli - if needed. - - Returns: - bool: True if elastic moduli are already present or if calculation - successfully ended, False otherwise. - """ - self.bulkModulus: npt.NDArray[ np.float64 ] - self.shearModulus: npt.NDArray[ np.float64 ] - self.youngModulus: npt.NDArray[ np.float64 ] - self.poissonRatio: npt.NDArray[ np.float64 ] - if self.computeYoungPoisson: - return self.computeElasticModulusFromBulkShear() - return self.computeElasticModulusFromYoungPoisson() - - def computeElasticModulusFromBulkShear( self: Self ) -> bool: - """Compute Young modulus Poisson's ratio from bulk and shear moduli. - - Returns: - bool: True if calculation successfully ended, False otherwise - """ - self.bulkModulus = getArrayInObject( self.output, self.bulkModulusAttributeName, self.bulkModulusOnPoints ) - self.shearModulus = getArrayInObject( self.output, self.shearModulusAttributeName, self.shearModulusOnPoints ) - - self.youngModulus = fcts.youngModulus( self.bulkModulus, self.shearModulus ) - if np.any( self.youngModulus < 0 ): - self.logger.error( "Young modulus yields negative values. Check Bulk and Shear modulus values." ) - return False - - if not createAttribute( self.output, - self.youngModulus, - self.youngModulusAttributeName, - onPoints=self.youngModulusOnPoints, - logger=self.logger ): - self.logger.error( "Young modulus computation failed." ) - return False - - self.poissonRatio = fcts.poissonRatio( self.bulkModulus, self.shearModulus ) - if np.any( self.poissonRatio < 0 ): - self.logger.error( "Poisson ratio yields negative values. Check Bulk and Shear modulus values." ) - return False - - if not createAttribute( self.output, - self.poissonRatio, - self.poissonRatioAttributeName, - onPoints=self.poissonRatioOnPoints, - logger=self.logger ): - self.logger.error( "Poisson ration computation failed." ) - return False - - return True - - def computeElasticModulusFromYoungPoisson( self: Self ) -> bool: - """Compute bulk modulus from Young Modulus and Poisson's ratio. - - Returns: - bool: True if bulk modulus was successfully computed, False otherwise - """ - self.youngModulus = getArrayInObject( self.output, self.youngModulusAttributeName, self.youngModulusOnPoints ) - self.poissonRatio = getArrayInObject( self.output, self.poissonRatioAttributeName, self.poissonRatioOnPoints ) - - self.bulkModulus = fcts.bulkModulus( self.youngModulus, self.poissonRatio ) - if np.any( self.bulkModulus < 0 ): - self.logger.error( "Bulk modulus yields negative values. Check Young modulus and Poisson ratio values." ) - return False - - if not createAttribute( self.output, - self.bulkModulus, - self.bulkModulusAttributeName, - onPoints=self.bulkModulusOnPoints, - logger=self.logger ): - self.logger.error( "Bulk modulus computation failed." ) - return False - - self.shearModulus = fcts.shearModulus( self.youngModulus, self.poissonRatio ) - if np.any( self.shearModulus < 0 ): - self.logger.error( "Shear modulus yields negative values. Check Young modulus and Poisson ratio values." ) - return False - - if not createAttribute( self.output, - self.shearModulus, - self.shearModulusAttributeName, - onPoints=self.shearModulusOnPoints, - logger=self.logger ): - self.logger.error( "Shear modulus computation failed." ) - return False + # Create an attribute on the mesh for each advanced outputs + for advancedOutput, array in self._advancedOutput.dictAdvancedOutput.items(): + if advancedOutput not in self._outputPresent and not createAttribute( self.output, array, advancedOutput.attributeName, onPoints=advancedOutput.isOnPoints, logger=self.logger ): + return False + self.logger.info( "All geomechanical advanced outputs were successfully computed." ) return True - def computeBiotCoefficient( self: Self ) -> bool: + def _computeBiotCoefficient( self: Self ) -> bool: """Compute Biot coefficient from default and grain bulk modulus. Returns: bool: True if calculation successfully ended, False otherwise. """ - biotCoefficientAttributeName: str = PostProcessingOutputsEnum.BIOT_COEFFICIENT.attributeName - biotCoefficientOnPoints: bool = PostProcessingOutputsEnum.BIOT_COEFFICIENT.isOnPoints - self.biotCoefficient: npt.NDArray[ np.float64 ] - - if not isAttributeInObject( self.output, biotCoefficientAttributeName, biotCoefficientOnPoints ): - self.biotCoefficient = fcts.biotCoefficient( self.grainBulkModulus, self.bulkModulus ) - return createAttribute( self.output, - self.biotCoefficient, - biotCoefficientAttributeName, - onPoints=biotCoefficientOnPoints, - logger=self.logger ) + if not isAttributeInObject( self.output, BIOT_COEFFICIENT.attributeName, BIOT_COEFFICIENT.isOnPoints ): + self._basicOutput.biotCoefficient = fcts.biotCoefficient( self.physicalConstants.grainBulkModulus, + self._elasticModuli.bulkModulus ) else: - self.biotCoefficient = getArrayInObject( self.output, biotCoefficientAttributeName, - biotCoefficientOnPoints ) + self._outputPresent.append( BIOT_COEFFICIENT.attributeName ) + self._basicOutput.biotCoefficient = getArrayInObject( self.output, BIOT_COEFFICIENT.attributeName, + BIOT_COEFFICIENT.isOnPoints ) self.logger.warning( - f"{ biotCoefficientAttributeName } is already on the mesh, it has not been computed by the filter." ) - return True + f"{ BIOT_COEFFICIENT.attributeName } is already on the mesh, it has not been computed by the filter." ) + + return True - def computeCompressibilityCoefficient( self: Self ) -> bool: + def _computeCompressibilityCoefficient( self: Self ) -> bool: """Compute compressibility coefficient from simulation outputs. Compressibility coefficient is computed from Poisson's ratio, bulk @@ -556,47 +794,47 @@ def computeCompressibilityCoefficient( self: Self ) -> bool: Returns: bool: True if the attribute is correctly created, False otherwise. """ - compressibilityAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY.attributeName - compressibilityOnPoints: bool = PostProcessingOutputsEnum.COMPRESSIBILITY.isOnPoints - compressibility: npt.NDArray[ np.float64 ] = fcts.compressibility( self.poissonRatio, self.bulkModulus, - self.biotCoefficient, self.porosity ) - if not createAttribute( self.output, - compressibility, - compressibilityAttributeName, - onPoints=compressibilityOnPoints, - logger=self.logger ): - self.logger.error( "Compressibility coefficient computation failed." ) - return False + if not isAttributeInObject( self.output, COMPRESSIBILITY.attributeName, COMPRESSIBILITY.isOnPoints ): + self._basicOutput.compressibility = fcts.compressibility( self._elasticModuli.poissonRatio, + self._elasticModuli.bulkModulus, + self._basicOutput.biotCoefficient, + self._mandatoryAttributes.porosity ) + else: + self._outputPresent.append( COMPRESSIBILITY.attributeName ) + self._basicOutput.compressibility = getArrayInObject( self.output, COMPRESSIBILITY.attributeName, + COMPRESSIBILITY.isOnPoints ) + self.logger.warning( + f"{ COMPRESSIBILITY.attributeName } is already on the mesh, it has not been computed by the filter." ) # oedometric compressibility - compressibilityOedAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY_OED.attributeName - compressibilityOedOnPoints: bool = PostProcessingOutputsEnum.COMPRESSIBILITY_OED.isOnPoints - compressibilityOed: npt.NDArray[ np.float64 ] = fcts.compressibilityOed( self.shearModulus, self.bulkModulus, - self.porosity ) - if not createAttribute( self.output, - compressibilityOed, - compressibilityOedAttributeName, - onPoints=compressibilityOedOnPoints, - logger=self.logger ): - self.logger.error( "Oedometric compressibility coefficient computation failed." ) - return False + if not isAttributeInObject( self.output, COMPRESSIBILITY_OED.attributeName, COMPRESSIBILITY_OED.isOnPoints ): + self._basicOutput.compressibilityOed = fcts.compressibilityOed( self._elasticModuli.shearModulus, + self._elasticModuli.bulkModulus, + self._mandatoryAttributes.porosity ) + else: + self._outputPresent.append( COMPRESSIBILITY_OED.attributeName ) + self._basicOutput.compressibilityOed = getArrayInObject( self.output, COMPRESSIBILITY_OED.attributeName, + COMPRESSIBILITY_OED.isOnPoints ) + self.logger.warning( + f"{ COMPRESSIBILITY_OED.attributeName } is already on the mesh, it has not been computed by the filter." + ) # real compressibility - compressibilityRealAttributeName: str = PostProcessingOutputsEnum.COMPRESSIBILITY_REAL.attributeName - compressibilityRealOnPoint: bool = PostProcessingOutputsEnum.COMPRESSIBILITY_REAL.isOnPoints - compressibilityReal: npt.NDArray[ np.float64 ] = fcts.compressibilityReal( self.deltaPressure, self.porosity, - self.porosityInitial ) - if not createAttribute( self.output, - compressibilityReal, - compressibilityRealAttributeName, - onPoints=compressibilityRealOnPoint, - logger=self.logger ): - self.logger.error( "Real compressibility coefficient computation failed." ) - return False + if not isAttributeInObject( self.output, COMPRESSIBILITY_REAL.attributeName, COMPRESSIBILITY_REAL.isOnPoints ): + self._basicOutput.compressibilityReal = fcts.compressibilityReal( + self._mandatoryAttributes.deltaPressure, self._mandatoryAttributes.porosity, + self._mandatoryAttributes.porosityInitial ) + else: + self._outputPresent.append( COMPRESSIBILITY_REAL.attributeName ) + self._basicOutput.compressibilityReal = getArrayInObject( self.output, COMPRESSIBILITY_REAL.attributeName, + COMPRESSIBILITY_REAL.isOnPoints ) + self.logger.warning( + f"{ COMPRESSIBILITY_REAL.attributeName } is already on the mesh, it has not been computed by the filter." + ) return True - def computeSpecificGravity( self: Self ) -> bool: + def _computeSpecificGravity( self: Self ) -> bool: """Create Specific gravity attribute. Specific gravity is computed from rock density attribute and specific @@ -605,96 +843,52 @@ def computeSpecificGravity( self: Self ) -> bool: Returns: bool: True if the attribute is correctly created, False otherwise. """ - specificGravityAttributeName: str = PostProcessingOutputsEnum.SPECIFIC_GRAVITY.attributeName - specificGravityOnPoints: bool = PostProcessingOutputsEnum.SPECIFIC_GRAVITY.isOnPoints - specificGravity: npt.NDArray[ np.float64 ] = fcts.specificGravity( self.density, self.specificDensity ) - return createAttribute( self.output, - specificGravity, - specificGravityAttributeName, - onPoints=specificGravityOnPoints, - logger=self.logger ) - - def computeRealEffectiveStressRatio( self: Self ) -> bool: - """Compute effective stress ratio. - - Returns: - bool: True if calculation successfully ended, False otherwise. - """ - return self.computeStressRatioReal( self.effectiveStress, - PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_REAL ) + if not isAttributeInObject( self.output, SPECIFIC_GRAVITY.attributeName, SPECIFIC_GRAVITY.isOnPoints ): + self._basicOutput.specificGravity = fcts.specificGravity( self._mandatoryAttributes.density, + self.physicalConstants.specificDensity ) + else: + self._outputPresent.append( SPECIFIC_GRAVITY.attributeName ) + self._basicOutput.specificGravity = getArrayInObject( self.output, SPECIFIC_GRAVITY.attributeName, + SPECIFIC_GRAVITY.isOnPoints ) + self.logger.warning( + f"{ SPECIFIC_GRAVITY.attributeName } is already on the mesh, it has not been computed by the filter." ) - def computeTotalStresses( self: Self ) -> bool: - """Compute total stress total stress ratio. + return True - Total stress is computed at the initial and current time steps. - Total stress ratio is computed at current time step only. + def _doComputeStressRatioReal( self: Self, stress: npt.NDArray[ np.float64 ], + basicOutput: AttributeEnum ) -> npt.NDArray[ np.float64 ]: + """Compute the ratio between horizontal and vertical effective stress. Returns: - bool: True if calculation successfully ended, False otherwise. + bool: return True if calculation successfully ended, False otherwise. """ - # Compute total stress at initial time step. - if not self.computeTotalStressInitial(): - self.logger.error( "Total stress at initial time step computation failed." ) - return False - - # Compute total stress at current time step. - totalStressAttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL.attributeName - totalStressOnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL.isOnPoints - self.totalStress: npt.NDArray[ np.float64 ] = self.doComputeTotalStress( self.effectiveStress, self.pressure, - self.biotCoefficient ) - if not createAttribute( self.output, - self.totalStress, - totalStressAttributeName, - componentNames=ComponentNameEnum.XYZ.value, - onPoints=totalStressOnPoints, - logger=self.logger ): - self.logger.error( "Total stress at current time step computation failed." ) - return False + verticalStress: npt.NDArray[ np.float64 ] = stress[ :, 2 ] + # keep the minimum of the 2 horizontal components + horizontalStress: npt.NDArray[ np.float64 ] = np.min( stress[ :, :2 ], axis=1 ) - # Compute total stress ratio. - if not self.computeStressRatioReal( self.totalStress, PostProcessingOutputsEnum.STRESS_TOTAL_RATIO_REAL ): - self.logger.error( "Total stress ratio computation failed." ) - return False + stressRatioReal: npt.NDArray[ np.float64 ] + if not isAttributeInObject( self.output, basicOutput.attributeName, basicOutput.isOnPoints ): + stressRatioReal = fcts.stressRatio( horizontalStress, verticalStress ) + else: + self._outputPresent.append( basicOutput.attributeName ) + stressRatioReal = getArrayInObject( self.output, basicOutput.attributeName, basicOutput.isOnPoints ) + self.logger.warning( + f"{ basicOutput.attributeName } is already on the mesh, it has not been computed by the filter." ) - return True + return stressRatioReal - def computeTotalStressInitial( self: Self ) -> bool: - """Compute total stress at initial time step. + def _computeRealEffectiveStressRatio( self: Self ) -> bool: + """Compute effective stress ratio. Returns: bool: True if calculation successfully ended, False otherwise. """ - bulkModulusT0: npt.NDArray[ np.float64 ] - if self.computeInitialBulkModulus: - youngModulusT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, self.youngModulusT0AttributeName, - self.youngModulusT0OnPoints ) - poissonRatioT0: npt.NDArray[ np.float64 ] = getArrayInObject( self.output, self.poissonRatioT0AttributeName, - self.poissonRatioT0OnPoints ) - bulkModulusT0 = fcts.bulkModulus( youngModulusT0, poissonRatioT0 ) - else: - bulkModulusT0 = getArrayInObject( self.output, self.bulkModulusT0AttributeName, self.bulkModulusT0OnPoints ) + self._basicOutput.effectiveStressRatioReal = self._doComputeStressRatioReal( + self._mandatoryAttributes.effectiveStress, STRESS_EFFECTIVE_RATIO_REAL ) - # Compute Biot at initial time step. - biotCoefficientT0: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.grainBulkModulus, bulkModulusT0 ) + return True - pressureT0: Union[ npt.NDArray[ np.float64 ], None ] = None - # Case pressureT0 is None, total stress = effective stress - # (managed by doComputeTotalStress function) - if self.pressure is not None: - # Get delta pressure to recompute pressure at initial time step (pressureTo) - pressureT0 = self.pressure - self.deltaPressure - - totalStressT0AttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.attributeName - totalStressT0OnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL_INITIAL.isOnPoints - self.totalStressT0 = self.doComputeTotalStress( self.effectiveStressT0, pressureT0, biotCoefficientT0 ) - return createAttribute( self.output, - self.totalStressT0, - totalStressT0AttributeName, - componentNames=ComponentNameEnum.XYZ.value, - onPoints=totalStressT0OnPoints, - logger=self.logger ) - - def doComputeTotalStress( + def _doComputeTotalStress( self: Self, effectiveStress: npt.NDArray[ np.float64 ], pressure: Union[ npt.NDArray[ np.float64 ], None ], @@ -723,32 +917,96 @@ def doComputeTotalStress( return totalStress + def _computeTotalStressInitial( self: Self ) -> bool: + """Compute total stress at initial time step. + + Returns: + bool: True if calculation successfully ended, False otherwise. + """ + # Compute Biot at initial time step. + biotCoefficientT0: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.physicalConstants.grainBulkModulus, + self._elasticModuli.bulkModulusT0 ) + + pressureT0: Union[ npt.NDArray[ np.float64 ], None ] = None + # Case pressureT0 is None, total stress = effective stress + # (managed by doComputeTotalStress function) + if self._mandatoryAttributes.pressure is not None: + # Get delta pressure to recompute pressure at initial time step (pressureTo) + pressureT0 = self._mandatoryAttributes.pressure - self._mandatoryAttributes.deltaPressure + + if not isAttributeInObject( self.output, STRESS_TOTAL_T0.attributeName, STRESS_TOTAL_T0.isOnPoints ): + self._basicOutput.totalStressT0 = self._doComputeTotalStress( self._mandatoryAttributes.effectiveStressT0, + pressureT0, biotCoefficientT0 ) + else: + self._outputPresent.append( STRESS_TOTAL_T0.attributeName ) + self._basicOutput.totalStressT0 = getArrayInObject( self.output, STRESS_TOTAL_T0.attributeName, + STRESS_TOTAL_T0.isOnPoints ) + self.logger.warning( + f"{ STRESS_TOTAL_T0.attributeName } is already on the mesh, it has not been computed by the filter." ) + + return True + + def _computeTotalStresses( self: Self ) -> bool: + """Compute total stress total stress ratio. + + Total stress is computed at the initial and current time steps. + Total stress ratio is computed at current time step only. + + Returns: + bool: True if calculation successfully ended, False otherwise. + """ + # Compute total stress at initial time step. + if not self._computeTotalStressInitial(): + self.logger.error( "Total stress at initial time step computation failed." ) + return False + + # Compute total stress at current time step. + if not isAttributeInObject( self.output, STRESS_TOTAL.attributeName, STRESS_TOTAL.isOnPoints ): + self._basicOutput.totalStress = self._doComputeTotalStress( self._mandatoryAttributes.effectiveStress, + self._mandatoryAttributes.pressure, + self._basicOutput.biotCoefficient ) + else: + self._outputPresent.append( STRESS_TOTAL.attributeName ) + self._basicOutput.totalStress = getArrayInObject( self.output, STRESS_TOTAL.attributeName, + STRESS_TOTAL.isOnPoints ) + self.logger.warning( + f"{ STRESS_TOTAL.attributeName } is already on the mesh, it has not been computed by the filter." ) + + # Compute total stress ratio. + self._basicOutput.totalStressRatioReal = self._doComputeStressRatioReal( self._basicOutput.totalStress, + STRESS_TOTAL_RATIO_REAL ) + + return True + def computeLithostaticStress( self: Self ) -> bool: """Compute lithostatic stress. Returns: bool: True if calculation successfully ended, False otherwise. """ - lithostaticStressAttributeName: str = PostProcessingOutputsEnum.LITHOSTATIC_STRESS.attributeName - lithostaticStressOnPoint: bool = PostProcessingOutputsEnum.LITHOSTATIC_STRESS.isOnPoints - - depth: npt.NDArray[ - np.float64 ] = self.computeDepthAlongLine() if lithostaticStressOnPoint else self.computeDepthInMesh() - lithostaticStress = fcts.lithostaticStress( depth, self.density, GRAVITY ) - return createAttribute( self.output, - lithostaticStress, - lithostaticStressAttributeName, - onPoints=lithostaticStressOnPoint, - logger=self.logger ) - - def computeDepthAlongLine( self: Self ) -> npt.NDArray[ np.float64 ]: + if not isAttributeInObject( self.output, LITHOSTATIC_STRESS.attributeName, LITHOSTATIC_STRESS.isOnPoints ): + depth: npt.NDArray[ np.float64 ] = self._doComputeDepthAlongLine( + ) if LITHOSTATIC_STRESS.isOnPoints else self._doComputeDepthInMesh() + self._basicOutput.lithostaticStress = fcts.lithostaticStress( depth, self._mandatoryAttributes.density, + GRAVITY ) + else: + self._outputPresent.append( LITHOSTATIC_STRESS.attributeName ) + self._basicOutput.lithostaticStress = getArrayInObject( self.output, LITHOSTATIC_STRESS.attributeName, + LITHOSTATIC_STRESS.isOnPoints ) + self.logger.warning( + f"{ LITHOSTATIC_STRESS.attributeName } is already on the mesh, it has not been computed by the filter." + ) + + return True + + def _doComputeDepthAlongLine( self: Self ) -> npt.NDArray[ np.float64 ]: """Compute depth along a line. Returns: npt.NDArray[np.float64]: 1D array with depth property """ # get z coordinate - zCoord: npt.NDArray[ np.float64 ] = self.getZcoordinates( True ) + zCoord: npt.NDArray[ np.float64 ] = self._getZcoordinates( True ) assert zCoord is not None, "Depth coordinates cannot be computed." # TODO: to find how to compute depth in a general case @@ -756,21 +1014,21 @@ def computeDepthAlongLine( self: Self ) -> npt.NDArray[ np.float64 ]: depth: npt.NDArray[ np.float64 ] = -1.0 * zCoord return depth - def computeDepthInMesh( self: Self ) -> npt.NDArray[ np.float64 ]: + def _doComputeDepthInMesh( self: Self ) -> npt.NDArray[ np.float64 ]: """Compute depth of each cell in a mesh. Returns: npt.NDArray[np.float64]: array with depth property """ # get z coordinate - zCoord: npt.NDArray[ np.float64 ] = self.getZcoordinates( False ) + zCoord: npt.NDArray[ np.float64 ] = self._getZcoordinates( False ) assert zCoord is not None, "Depth coordinates cannot be computed." # TODO: to find how to compute depth in a general case depth: npt.NDArray[ np.float64 ] = -1.0 * zCoord return depth - def getZcoordinates( self: Self, onPoints: bool ) -> npt.NDArray[ np.float64 ]: + def _getZcoordinates( self: Self, onPoints: bool ) -> npt.NDArray[ np.float64 ]: """Get z coordinates from self.output. Args: @@ -781,204 +1039,190 @@ def getZcoordinates( self: Self, onPoints: bool ) -> npt.NDArray[ np.float64 ]: """ # get z coordinate zCoord: npt.NDArray[ np.float64 ] - pointCoords: npt.NDArray[ np.float64 ] = self.getPointCoordinates( onPoints ) + pointCoords: npt.NDArray[ np.float64 ] = self._getPointCoordinates( onPoints ) assert pointCoords is not None, "Point coordinates are undefined." assert pointCoords.shape[ 1 ] == 2, "Point coordinates are undefined." zCoord = pointCoords[ :, 2 ] return zCoord - def computeElasticStrain( self: Self ) -> bool: + def _getPointCoordinates( self: Self, onPoints: bool ) -> npt.NDArray[ np.float64 ]: + """Get the coordinates of Points or Cell center. + + Args: + onPoints (bool): True if the attribute is on points, False if it is on cells. + + Returns: + npt.NDArray[np.float64]: points/cell center coordinates + """ + if onPoints: + return self.output.GetPoints() # type: ignore[no-any-return] + else: + # Find cell centers + filter = vtkCellCenters() + filter.SetInputDataObject( self.output ) + filter.Update() + return filter.GetOutput().GetPoints() # type: ignore[no-any-return] + + def _computeElasticStrain( self: Self ) -> bool: """Compute elastic strain from effective stress and elastic modulus. Returns: bool: return True if calculation successfully ended, False otherwise. """ - deltaEffectiveStress = self.effectiveStress - self.effectiveStressT0 + deltaEffectiveStress = self._mandatoryAttributes.effectiveStress - self._mandatoryAttributes.effectiveStressT0 - elasticStrainAttributeName: str = PostProcessingOutputsEnum.STRAIN_ELASTIC.attributeName - elasticStrainOnPoints: bool = PostProcessingOutputsEnum.STRAIN_ELASTIC.isOnPoints - elasticStrain: npt.NDArray[ np.float64 ] - if self.computeYoungPoisson: - elasticStrain = fcts.elasticStrainFromBulkShear( deltaEffectiveStress, self.bulkModulus, self.shearModulus ) + if not isAttributeInObject( self.output, STRAIN_ELASTIC.attributeName, STRAIN_ELASTIC.isOnPoints ): + if self.computeYoungPoisson: + self._basicOutput.elasticStrain = fcts.elasticStrainFromBulkShear( deltaEffectiveStress, + self._elasticModuli.bulkModulus, + self._elasticModuli.shearModulus ) + else: + self._basicOutput.elasticStrain = fcts.elasticStrainFromYoungPoisson( + deltaEffectiveStress, self._elasticModuli.youngModulus, self._elasticModuli.poissonRatio ) else: - elasticStrain = fcts.elasticStrainFromYoungPoisson( deltaEffectiveStress, self.youngModulus, - self.poissonRatio ) + self._outputPresent.append( STRAIN_ELASTIC.attributeName ) + self._basicOutput.totalStressT0 = getArrayInObject( self.output, STRAIN_ELASTIC.attributeName, + STRAIN_ELASTIC.isOnPoints ) + self.logger.warning( + f"{ STRAIN_ELASTIC.attributeName } is already on the mesh, it has not been computed by the filter." ) - return createAttribute( self.output, - elasticStrain, - elasticStrainAttributeName, - componentNames=ComponentNameEnum.XYZ.value, - onPoints=elasticStrainOnPoints, - logger=self.logger ) + return True - def computeReservoirStressPathReal( self: Self ) -> bool: + def _computeReservoirStressPathReal( self: Self ) -> bool: """Compute reservoir stress paths. Returns: bool: True if calculation successfully ended, False otherwise. """ # create delta stress attribute for QC - deltaTotalStressAttributeName: str = PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.attributeName - deltaTotalStressOnPoints: bool = PostProcessingOutputsEnum.STRESS_TOTAL_DELTA.isOnPoints - self.deltaTotalStress: npt.NDArray[ np.float64 ] = self.totalStress - self.totalStressT0 - if not createAttribute( self.output, - self.deltaTotalStress, - deltaTotalStressAttributeName, - componentNames=ComponentNameEnum.XYZ.value, - onPoints=deltaTotalStressOnPoints, - logger=self.logger ): - self.logger.error( "Delta total stress computation failed." ) - return False + if not isAttributeInObject( self.output, STRESS_TOTAL_DELTA.attributeName, STRESS_TOTAL_DELTA.isOnPoints ): + self._basicOutput.deltaTotalStress = self._basicOutput.totalStress - self._basicOutput.totalStressT0 + else: + self._outputPresent.append( STRESS_TOTAL_DELTA.attributeName ) + self._basicOutput.deltaTotalStress = getArrayInObject( self.output, STRESS_TOTAL_DELTA.attributeName, + STRESS_TOTAL_DELTA.isOnPoints ) + self.logger.warning( + f"{ STRESS_TOTAL_DELTA.attributeName } is already on the mesh, it has not been computed by the filter." + ) - rspRealAttributeName: str = PostProcessingOutputsEnum.RSP_REAL.attributeName - rspRealOnPoints: bool = PostProcessingOutputsEnum.RSP_REAL.isOnPoints - self.rspReal: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathReal( self.deltaTotalStress, - self.deltaPressure ) - if not createAttribute( self.output, - self.rspReal, - rspRealAttributeName, - componentNames=ComponentNameEnum.XYZ.value, - onPoints=rspRealOnPoints, - logger=self.logger ): - self.logger.error( "Reservoir stress real path computation failed." ) - return False + if not isAttributeInObject( self.output, RSP_REAL.attributeName, RSP_REAL.isOnPoints ): + self._basicOutput.rspReal = fcts.reservoirStressPathReal( self._basicOutput.deltaTotalStress, + self._mandatoryAttributes.deltaPressure ) + else: + self._outputPresent.append( RSP_REAL.attributeName ) + self._basicOutput.rspReal = getArrayInObject( self.output, RSP_REAL.attributeName, RSP_REAL.isOnPoints ) + self.logger.warning( + f"{ RSP_REAL.attributeName } is already on the mesh, it has not been computed by the filter." ) return True - def computeReservoirStressPathOed( self: Self ) -> bool: + def _computeReservoirStressPathOed( self: Self ) -> bool: """Compute Reservoir Stress Path in oedometric conditions. Returns: bool: return True if calculation successfully ended, False otherwise. """ - rspOedAttributeName: str = PostProcessingOutputsEnum.RSP_OED.attributeName - rspOedOnPoints: bool = PostProcessingOutputsEnum.RSP_OED.isOnPoints - self.rspOed: npt.NDArray[ np.float64 ] = fcts.reservoirStressPathOed( self.biotCoefficient, self.poissonRatio ) - return createAttribute( self.output, - self.rspOed, - rspOedAttributeName, - onPoints=rspOedOnPoints, - logger=self.logger ) - - def computeStressRatioReal( self: Self, stress: npt.NDArray[ np.float64 ], outputAttribute: AttributeEnum ) -> bool: - """Compute the ratio between horizontal and vertical effective stress. - - Returns: - bool: return True if calculation successfully ended, False otherwise. - """ - verticalStress: npt.NDArray[ np.float64 ] = stress[ :, 2 ] - # keep the minimum of the 2 horizontal components - horizontalStress: npt.NDArray[ np.float64 ] = np.min( stress[ :, :2 ], axis=1 ) + if not isAttributeInObject( self.output, RSP_OED.attributeName, RSP_OED.isOnPoints ): + self._basicOutput.rspOed = fcts.reservoirStressPathOed( self._basicOutput.biotCoefficient, + self._elasticModuli.poissonRatio ) + else: + self._outputPresent.append( RSP_OED.attributeName ) + self._basicOutput.rspOed = getArrayInObject( self.output, RSP_OED.attributeName, RSP_OED.isOnPoints ) + self.logger.warning( + f"{ RSP_OED.attributeName } is already on the mesh, it has not been computed by the filter." ) - stressRatioRealAttributeName: str = outputAttribute.attributeName - stressRatioRealOnPoints: bool = outputAttribute.isOnPoints - self.stressRatioReal: npt.NDArray[ np.float64 ] = fcts.stressRatio( horizontalStress, verticalStress ) - return createAttribute( self.output, - self.stressRatioReal, - stressRatioRealAttributeName, - onPoints=stressRatioRealOnPoints, - logger=self.logger ) + return True - def computeEffectiveStressRatioOed( self: Self ) -> bool: + def _computeEffectiveStressRatioOed( self: Self ) -> bool: """Compute the effective stress ratio in oedometric conditions. Returns: bool: True if calculation successfully ended, False otherwise. """ - effectiveStressRatioOedAttributeName: str = PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_OED.attributeName - effectiveStressRatioOedOnPoints: bool = PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_OED.isOnPoints - effectiveStressRatioOed: npt.NDArray[ np.float64 ] = fcts.deviatoricStressPathOed( self.poissonRatio ) - return createAttribute( self.output, - effectiveStressRatioOed, - effectiveStressRatioOedAttributeName, - onPoints=effectiveStressRatioOedOnPoints, - logger=self.logger ) - - def computeCriticalTotalStressRatio( self: Self ) -> bool: + if not isAttributeInObject( self.output, STRESS_EFFECTIVE_RATIO_OED.attributeName, + STRESS_EFFECTIVE_RATIO_OED.isOnPoints ): + self._basicOutput.effectiveStressRatioOed = fcts.deviatoricStressPathOed( self._elasticModuli.poissonRatio ) + else: + self._outputPresent.append( STRESS_EFFECTIVE_RATIO_OED.attributeName ) + self._basicOutput.effectiveStressRatioOed = getArrayInObject( self.output, + STRESS_EFFECTIVE_RATIO_OED.attributeName, + STRESS_EFFECTIVE_RATIO_OED.isOnPoints ) + self.logger.warning( + f"{ STRESS_EFFECTIVE_RATIO_OED.attributeName } is already on the mesh, it has not been computed by the filter." + ) + + return True + + def _computeCriticalTotalStressRatio( self: Self ) -> bool: """Compute fracture index and fracture threshold. Returns: bool: return True if calculation successfully ended, False otherwise. """ - fractureIndexAttributeName: str = PostProcessingOutputsEnum.CRITICAL_TOTAL_STRESS_RATIO.attributeName - fractureIndexOnPoints: bool = PostProcessingOutputsEnum.CRITICAL_TOTAL_STRESS_RATIO.isOnPoints - verticalStress: npt.NDArray[ np.float64 ] = self.totalStress[ :, 2 ] - criticalTotalStressRatio: npt.NDArray[ np.float64 ] = fcts.criticalTotalStressRatio( - self.pressure, verticalStress ) - if not createAttribute( self.output, - criticalTotalStressRatio, - fractureIndexAttributeName, - onPoints=fractureIndexOnPoints, - logger=self.logger ): - self.logger.error( "Fracture index computation failed." ) - return False + if not isAttributeInObject( self.output, CRITICAL_TOTAL_STRESS_RATIO.attributeName, + CRITICAL_TOTAL_STRESS_RATIO.isOnPoints ): + verticalStress: npt.NDArray[ np.float64 ] = self._basicOutput.totalStress[ :, 2 ] + self._advancedOutput.criticalTotalStressRatio = fcts.criticalTotalStressRatio( + self._mandatoryAttributes.pressure, verticalStress ) + else: + self._outputPresent.append( CRITICAL_TOTAL_STRESS_RATIO.attributeName ) + self._advancedOutput.criticalTotalStressRatio = getArrayInObject( self.output, + CRITICAL_TOTAL_STRESS_RATIO.attributeName, + CRITICAL_TOTAL_STRESS_RATIO.isOnPoints ) + self.logger.warning( + f"{ CRITICAL_TOTAL_STRESS_RATIO.attributeName } is already on the mesh, it has not been computed by the filter." + ) - mask: npt.NDArray[ np.bool_ ] = np.argmin( np.abs( self.totalStress[ :, :2 ] ), axis=1 ) - horizontalStress: npt.NDArray[ np.float64 ] = self.totalStress[ :, :2 ][ - np.arange( self.totalStress[ :, :2 ].shape[ 0 ] ), mask ] - - stressRatioThreshold: npt.NDArray[ np.float64 ] = fcts.totalStressRatioThreshold( - self.pressure, horizontalStress ) - fractureThresholdAttributeName: str = PostProcessingOutputsEnum.TOTAL_STRESS_RATIO_THRESHOLD.attributeName - fractureThresholdOnPoints: bool = PostProcessingOutputsEnum.TOTAL_STRESS_RATIO_THRESHOLD.isOnPoints - if not createAttribute( self.output, - stressRatioThreshold, - fractureThresholdAttributeName, - onPoints=fractureThresholdOnPoints, - logger=self.logger ): - self.logger.error( "Fracture threshold computation failed." ) - return False + if not isAttributeInObject( self.output, TOTAL_STRESS_RATIO_THRESHOLD.attributeName, + TOTAL_STRESS_RATIO_THRESHOLD.isOnPoints ): + mask: npt.NDArray[ np.bool_ ] = np.argmin( np.abs( self._basicOutput.totalStress[ :, :2 ] ), axis=1 ) + horizontalStress: npt.NDArray[ np.float64 ] = self._basicOutput.totalStress[ :, :2 ][ + np.arange( self._basicOutput.totalStress[ :, :2 ].shape[ 0 ] ), mask ] + self._advancedOutput.stressRatioThreshold = fcts.totalStressRatioThreshold( + self._mandatoryAttributes.pressure, horizontalStress ) + else: + self._outputPresent.append( TOTAL_STRESS_RATIO_THRESHOLD.attributeName ) + self._advancedOutput.stressRatioThreshold = getArrayInObject( self.output, + TOTAL_STRESS_RATIO_THRESHOLD.attributeName, + TOTAL_STRESS_RATIO_THRESHOLD.isOnPoints ) + self.logger.warning( + f"{ TOTAL_STRESS_RATIO_THRESHOLD.attributeName } is already on the mesh, it has not been computed by the filter." + ) return True - def computeCriticalPorePressure( self: Self ) -> bool: + def _computeCriticalPorePressure( self: Self ) -> bool: """Compute the critical pore pressure and the pressure index. Returns: bool: return True if calculation successfully ended, False otherwise. """ - criticalPorePressureAttributeName: str = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE.attributeName - criticalPorePressureOnPoints: bool = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE.isOnPoints - criticalPorePressure: npt.NDArray[ np.float64 ] = fcts.criticalPorePressure( -1.0 * self.totalStress, - self.rockCohesion, - self.frictionAngle ) - if not createAttribute( self.output, - criticalPorePressure, - criticalPorePressureAttributeName, - onPoints=criticalPorePressureOnPoints, - logger=self.logger ): - self.logger.error( "Critical pore pressure computation failed." ) - return False + if not isAttributeInObject( self.output, CRITICAL_PORE_PRESSURE.attributeName, + CRITICAL_PORE_PRESSURE.isOnPoints ): + self._advancedOutput.criticalPorePressure = fcts.criticalPorePressure( + -1.0 * self._basicOutput.totalStress, self.physicalConstants.rockCohesion, + self.physicalConstants.frictionAngle ) + else: + self._outputPresent.append( CRITICAL_PORE_PRESSURE.attributeName ) + self._advancedOutput.criticalPorePressure = getArrayInObject( self.output, + CRITICAL_PORE_PRESSURE.attributeName, + CRITICAL_PORE_PRESSURE.isOnPoints ) + self.logger.warning( + f"{ CRITICAL_PORE_PRESSURE.attributeName } is already on the mesh, it has not been computed by the filter." + ) - # add critical pore pressure index (i.e., ratio between pressure and criticalPorePressure) - criticalPorePressureIndex: npt.NDArray[ np.float64 ] = fcts.criticalPorePressureThreshold( - self.pressure, criticalPorePressure ) - criticalPorePressureIndexAttributeName: str = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName - criticalPorePressureIndexOnPoint: bool = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD.isOnPoints - if not createAttribute( self.output, - criticalPorePressureIndex, - criticalPorePressureIndexAttributeName, - onPoints=criticalPorePressureIndexOnPoint, - logger=self.logger ): - self.logger.error( "Critical pore pressure indexes computation failed." ) - return False + # Add critical pore pressure index (i.e., ratio between pressure and criticalPorePressure) + if not isAttributeInObject( self.output, CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName, + CRITICAL_PORE_PRESSURE_THRESHOLD.isOnPoints ): + self._advancedOutput.criticalPorePressureIndex = fcts.criticalPorePressureThreshold( + self._mandatoryAttributes.pressure, self._advancedOutput.criticalPorePressure ) + else: + self._outputPresent.append( CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName ) + self._advancedOutput.criticalPorePressureIndex = getArrayInObject( + self.output, CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName, + CRITICAL_PORE_PRESSURE_THRESHOLD.isOnPoints ) + self.logger.warning( + f"{ CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName } is already on the mesh, it has not been computed by the filter." + ) return True - - def getPointCoordinates( self: Self, onPoints: bool ) -> npt.NDArray[ np.float64 ]: - """Get the coordinates of Points or Cell center. - - Args: - onPoints (bool): True if the attribute is on points, False if it is on cells. - - Returns: - npt.NDArray[np.float64]: points/cell center coordinates - """ - if onPoints: - return self.output.GetPoints() # type: ignore[no-any-return] - else: - # Find cell centers - filter = vtkCellCenters() - filter.SetInputDataObject( self.output ) - filter.Update() - return filter.GetOutput().GetPoints() # type: ignore[no-any-return] diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py index f4d861c5d..b6f0affff 100644 --- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py +++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py @@ -274,10 +274,10 @@ def RequestData( if not filter.logger.hasHandlers(): filter.setLoggerHandler( VTKHandler() ) - filter.setGrainBulkModulus( self.grainBulkModulus ) - filter.setSpecificDensity( self.specificDensity ) - filter.setRockCohesion( self.rockCohesion ) - filter.setFrictionAngle( self.frictionAngle ) + filter.physicalConstants.grainBulkModulus = self.grainBulkModulus + filter.physicalConstants.specificDensity = self.specificDensity + filter.physicalConstants.rockCohesion = self.rockCohesion + filter.physicalConstants.frictionAngle = self.frictionAngle if filter.applyFilter(): outputMesh.ShallowCopy( filter.getOutput() ) From 989c5ecc5d58a09735fe6ed16673d95b23c0d5ef Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 14 Oct 2025 16:11:02 +0200 Subject: [PATCH 36/40] update dataclass --- .../post_processing/GeomechanicsCalculator.py | 496 +++++++++++------- 1 file changed, 292 insertions(+), 204 deletions(-) diff --git a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py index c601c5296..3dbe8719d 100644 --- a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py +++ b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py @@ -4,7 +4,7 @@ # ruff: noqa: E402 # disable Module level import not at top of file from typing import Union from typing_extensions import Self -from dataclasses import dataclass, field +from dataclasses import dataclass import logging import numpy as np @@ -128,6 +128,8 @@ BULK_MODULUS_T0: AttributeEnum = PostProcessingOutputsEnum.BULK_MODULUS_INITIAL YOUNG_MODULUS_T0: AttributeEnum = PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL POISSON_RATIO_T0: AttributeEnum = PostProcessingOutputsEnum.POISSON_RATIO_INITIAL +ELASTIC_MODULI: tuple[ AttributeEnum, ...] = ( BULK_MODULUS, SHEAR_MODULUS, YOUNG_MODULUS, POISSON_RATIO, + BULK_MODULUS_T0, YOUNG_MODULUS_T0, POISSON_RATIO_T0 ) # Mandatory attributes: POROSITY: AttributeEnum = GeosMeshOutputsEnum.POROSITY @@ -137,6 +139,8 @@ DENSITY: AttributeEnum = GeosMeshOutputsEnum.ROCK_DENSITY STRESS_EFFECTIVE: AttributeEnum = GeosMeshOutputsEnum.STRESS_EFFECTIVE STRESS_EFFECTIVE_T0: AttributeEnum = PostProcessingOutputsEnum.STRESS_EFFECTIVE_INITIAL +MANDATORY_ATTRIBUTES: tuple[ AttributeEnum, ...] = ( POROSITY, POROSITY_T0, PRESSURE, DELTA_PRESSURE, DENSITY, + STRESS_EFFECTIVE, STRESS_EFFECTIVE_T0 ) # Basic outputs: BIOT_COEFFICIENT: AttributeEnum = PostProcessingOutputsEnum.BIOT_COEFFICIENT @@ -154,12 +158,19 @@ RSP_REAL: AttributeEnum = PostProcessingOutputsEnum.RSP_REAL RSP_OED: AttributeEnum = PostProcessingOutputsEnum.RSP_OED STRESS_EFFECTIVE_RATIO_OED: AttributeEnum = PostProcessingOutputsEnum.STRESS_EFFECTIVE_RATIO_OED +BASIC_OUTPUTS: tuple[ AttributeEnum, + ...] = ( BIOT_COEFFICIENT, COMPRESSIBILITY, COMPRESSIBILITY_OED, COMPRESSIBILITY_REAL, + SPECIFIC_GRAVITY, STRESS_EFFECTIVE_RATIO_REAL, STRESS_TOTAL, STRESS_TOTAL_T0, + STRESS_TOTAL_RATIO_REAL, LITHOSTATIC_STRESS, STRAIN_ELASTIC, STRESS_TOTAL_DELTA, + RSP_REAL, RSP_OED, STRESS_EFFECTIVE_RATIO_OED ) # Advanced outputs: CRITICAL_TOTAL_STRESS_RATIO: AttributeEnum = PostProcessingOutputsEnum.CRITICAL_TOTAL_STRESS_RATIO TOTAL_STRESS_RATIO_THRESHOLD: AttributeEnum = PostProcessingOutputsEnum.TOTAL_STRESS_RATIO_THRESHOLD CRITICAL_PORE_PRESSURE: AttributeEnum = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE CRITICAL_PORE_PRESSURE_THRESHOLD: AttributeEnum = PostProcessingOutputsEnum.CRITICAL_PORE_PRESSURE_THRESHOLD +ADVANCED_OUTPUTS: tuple[ AttributeEnum, ...] = ( CRITICAL_TOTAL_STRESS_RATIO, TOTAL_STRESS_RATIO_THRESHOLD, + CRITICAL_PORE_PRESSURE, CRITICAL_PORE_PRESSURE_THRESHOLD ) class GeomechanicsCalculator: @@ -208,288 +219,389 @@ def frictionAngle( self: Self, value: float ) -> None: physicalConstants: PhysicalConstants = PhysicalConstants() @dataclass - class MandatoryAttributes: - _dictMandatoryAttribute: dict[ AttributeEnum, npt.NDArray[ np.float64 ] ] = field( default_factory=dict ) - - @property - def dictMandatoryAttribute( self: Self ) -> dict[ AttributeEnum, npt.NDArray[ np.float64 ] ]: - return self._dictMandatoryAttribute - - @dictMandatoryAttribute.setter - def dictMandatoryAttribute( self: Self, attributeValue: tuple[ AttributeEnum, npt.NDArray[ np.float64 ] ] ) -> None: - self._dictMandatoryAttribute[ attributeValue[ 0 ] ] = attributeValue[ 1 ] - - @property - def porosity( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictMandatoryAttribute[ POROSITY ] - - @property - def porosityInitial( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictMandatoryAttribute[ POROSITY_T0 ] - - @property - def pressure( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictMandatoryAttribute[ PRESSURE ] - - @property - def deltaPressure( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictMandatoryAttribute[ DELTA_PRESSURE ] - - @property - def density( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictMandatoryAttribute[ DENSITY ] - - @property - def effectiveStress( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictMandatoryAttribute[ STRESS_EFFECTIVE ] - - @property - def effectiveStressT0( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictMandatoryAttribute[ STRESS_EFFECTIVE_T0 ] - - _mandatoryAttributes: MandatoryAttributes = MandatoryAttributes() - - @dataclass - class ElasticModuli: - _dictElasticModuli: dict[ AttributeEnum, npt.NDArray[ np.float64 ] ] = field( default_factory=dict) - - @property - def dictElasticModuli( self: Self ) -> dict[ AttributeEnum, npt.NDArray[ np.float64 ] ]: - return self._dictElasticModuli - - @dictElasticModuli.setter - def dictElasticModuli( self: Self, value: tuple[ AttributeEnum, npt.NDArray[ np.float64 ] ] ) -> None: - self._dictElasticModuli[ value[ 0 ] ] = value[ 1 ] + class ElasticModuliValue: + _bulkModulus: npt.NDArray[ np.float64 ] = np.array( [] ) + _shearModulus: npt.NDArray[ np.float64 ] = np.array( [] ) + _youngModulus: npt.NDArray[ np.float64 ] = np.array( [] ) + _poissonRatio: npt.NDArray[ np.float64 ] = np.array( [] ) + _bulkModulusT0: npt.NDArray[ np.float64 ] = np.array( [] ) + _youngModulusT0: npt.NDArray[ np.float64 ] = np.array( [] ) + _poissonRatioT0: npt.NDArray[ np.float64 ] = np.array( [] ) @property def bulkModulus( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictElasticModuli[ BULK_MODULUS ] + return self._bulkModulus @bulkModulus.setter def bulkModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictElasticModuli[ BULK_MODULUS ] = value + self._bulkModulus = value @property def shearModulus( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictElasticModuli[ SHEAR_MODULUS ] + return self._shearModulus @shearModulus.setter def shearModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictElasticModuli[ SHEAR_MODULUS ] = value + self._shearModulus = value @property def youngModulus( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictElasticModuli[ YOUNG_MODULUS ] + return self._youngModulus @youngModulus.setter def youngModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictElasticModuli[ YOUNG_MODULUS ] = value + self._youngModulus = value @property def poissonRatio( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictElasticModuli[ POISSON_RATIO ] + return self._poissonRatio @poissonRatio.setter def poissonRatio( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictElasticModuli[ POISSON_RATIO ] = value + self._poissonRatio = value @property def bulkModulusT0( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictElasticModuli[ BULK_MODULUS_T0 ] + return self._bulkModulusT0 @bulkModulusT0.setter def bulkModulusT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictElasticModuli[ BULK_MODULUS_T0 ] = value + self._bulkModulusT0 = value @property def youngModulusT0( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictElasticModuli[ YOUNG_MODULUS_T0 ] + return self._youngModulusT0 @youngModulusT0.setter def youngModulusT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictElasticModuli[ YOUNG_MODULUS_T0 ] = value + self._youngModulusT0 = value @property def poissonRatioT0( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictElasticModuli[ POISSON_RATIO_T0 ] + return self._poissonRatioT0 @poissonRatioT0.setter def poissonRatioT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictElasticModuli[ POISSON_RATIO_T0 ] = value + self._poissonRatioT0 = value + + def setElasticModulusValue( self: Self, name: str, value: npt.NDArray[ np.float64 ] ) -> None: + if name == BULK_MODULUS.attributeName: + self.bulkModulus = value + elif name == BULK_MODULUS_T0.attributeName: + self.bulkModulusT0 = value + elif name == SHEAR_MODULUS.attributeName: + self.shearModulus = value + elif name == YOUNG_MODULUS.attributeName: + self.youngModulus = value + elif name == YOUNG_MODULUS_T0.attributeName: + self.youngModulusT0 = value + elif name == POISSON_RATIO.attributeName: + self.poissonRatio = value + elif name == POISSON_RATIO_T0.attributeName: + self.poissonRatioT0 = value + + def getElasticModulusValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ]: + if name == BULK_MODULUS.attributeName: + return self.bulkModulus + elif name == BULK_MODULUS_T0.attributeName: + return self.bulkModulusT0 + elif name == SHEAR_MODULUS.attributeName: + return self.shearModulus + elif name == YOUNG_MODULUS.attributeName: + return self.youngModulus + elif name == YOUNG_MODULUS_T0.attributeName: + return self.youngModulusT0 + elif name == POISSON_RATIO.attributeName: + return self.poissonRatio + elif name == POISSON_RATIO_T0.attributeName: + return self.poissonRatioT0 + else: + raise NameError - _elasticModuli: ElasticModuli = ElasticModuli() + _elasticModuli: ElasticModuliValue = ElasticModuliValue() @dataclass - class BasicOutput: - _dictBasicOutput: dict[ AttributeEnum, npt.NDArray[ np.float64 ] ] = field( default_factory=dict ) + class MandatoryAttributesValue: + _porosity: npt.NDArray[ np.float64 ] = np.array( [] ) + _porosityInitial: npt.NDArray[ np.float64 ] = np.array( [] ) + _pressure: npt.NDArray[ np.float64 ] = np.array( [] ) + _deltaPressure: npt.NDArray[ np.float64 ] = np.array( [] ) + _density: npt.NDArray[ np.float64 ] = np.array( [] ) + _effectiveStress: npt.NDArray[ np.float64 ] = np.array( [] ) + _effectiveStressT0: npt.NDArray[ np.float64 ] = np.array( [] ) @property - def dictBasicOutput( self: Self ) -> dict[ AttributeEnum, npt.NDArray[ np.float64 ] ]: - return self._dictBasicOutput + def porosity( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._porosity + + @property + def porosityInitial( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._porosityInitial + + @property + def pressure( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._pressure + + @property + def deltaPressure( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._deltaPressure + + @property + def density( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._density + + @property + def effectiveStress( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._effectiveStress + + @property + def effectiveStressT0( self: Self ) -> npt.NDArray[ np.float64 ]: + return self._effectiveStressT0 + + def setMandatoryAttributeValue( self: Self, name: str, value: npt.NDArray[ np.float64 ] ) -> None: + if name == POROSITY.attributeName: + self._porosity = value + elif name == POROSITY_T0.attributeName: + self._porosityInitial = value + elif name == PRESSURE.attributeName: + self._pressure = value + elif name == DELTA_PRESSURE.attributeName: + self._deltaPressure = value + elif name == DENSITY.attributeName: + self._density = value + elif name == STRESS_EFFECTIVE.attributeName: + self._effectiveStress = value + elif name == STRESS_EFFECTIVE_T0.attributeName: + self._effectiveStressT0 = value + + _mandatoryAttributes: MandatoryAttributesValue = MandatoryAttributesValue() + + @dataclass + class BasicOutputValue: + _biotCoefficient: npt.NDArray[ np.float64 ] = np.array( [] ) + _compressibility: npt.NDArray[ np.float64 ] = np.array( [] ) + _compressibilityOed: npt.NDArray[ np.float64 ] = np.array( [] ) + _compressibilityReal: npt.NDArray[ np.float64 ] = np.array( [] ) + _specificGravity: npt.NDArray[ np.float64 ] = np.array( [] ) + _effectiveStressRatioReal: npt.NDArray[ np.float64 ] = np.array( [] ) + _totalStress: npt.NDArray[ np.float64 ] = np.array( [] ) + _totalStressT0: npt.NDArray[ np.float64 ] = np.array( [] ) + _totalStressRatioReal: npt.NDArray[ np.float64 ] = np.array( [] ) + # _lithostaticStress: npt.NDArray[ np.float64 ] = np.array( [] ) + _elasticStrain: npt.NDArray[ np.float64 ] = np.array( [] ) + _deltaTotalStress: npt.NDArray[ np.float64 ] = np.array( [] ) + _rspReal: npt.NDArray[ np.float64 ] = np.array( [] ) + _rspOed: npt.NDArray[ np.float64 ] = np.array( [] ) + _effectiveStressRatioOed: npt.NDArray[ np.float64 ] = np.array( [] ) @property def biotCoefficient( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ BIOT_COEFFICIENT ] + return self._biotCoefficient @biotCoefficient.setter def biotCoefficient( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ BIOT_COEFFICIENT ] = value + self._biotCoefficient = value @property def compressibility( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ COMPRESSIBILITY ] + return self._compressibility @compressibility.setter def compressibility( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ COMPRESSIBILITY ] = value + self._compressibility = value @property def compressibilityOed( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ COMPRESSIBILITY_OED ] + return self._compressibilityOed @compressibilityOed.setter def compressibilityOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ COMPRESSIBILITY_OED ] = value + self._compressibilityOed = value @property def compressibilityReal( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ COMPRESSIBILITY_REAL ] + return self._compressibilityReal @compressibilityReal.setter def compressibilityReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ COMPRESSIBILITY_REAL ] = value + self._compressibilityReal = value @property def specificGravity( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ SPECIFIC_GRAVITY ] + return self._specificGravity @specificGravity.setter def specificGravity( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ SPECIFIC_GRAVITY ] = value + self._specificGravity = value @property def effectiveStressRatioReal( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ STRESS_EFFECTIVE_RATIO_REAL ] + return self._effectiveStressRatioReal @effectiveStressRatioReal.setter def effectiveStressRatioReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ STRESS_EFFECTIVE_RATIO_REAL ] = value + self._effectiveStressRatioReal = value @property def totalStress( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ STRESS_TOTAL ] + return self._totalStress @totalStress.setter def totalStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ STRESS_TOTAL ] = value + self._totalStress = value @property def totalStressT0( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ STRESS_TOTAL_T0 ] + return self._totalStressT0 @totalStressT0.setter def totalStressT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ STRESS_TOTAL_T0 ] = value + self._totalStressT0 = value @property def totalStressRatioReal( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ STRESS_TOTAL_RATIO_REAL ] + return self._totalStressRatioReal @totalStressRatioReal.setter def totalStressRatioReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ STRESS_TOTAL_RATIO_REAL ] = value + self._totalStressRatioReal = value @property def lithostaticStress( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ LITHOSTATIC_STRESS ] + return self._lithostaticStress @lithostaticStress.setter def lithostaticStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ LITHOSTATIC_STRESS ] = value + self._lithostaticStress = value @property def elasticStrain( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ STRAIN_ELASTIC ] + return self._elasticStrain @elasticStrain.setter def elasticStrain( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ STRAIN_ELASTIC ] = value + self._elasticStrain = value @property def deltaTotalStress( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ STRESS_TOTAL_DELTA ] + return self._deltaTotalStress @deltaTotalStress.setter def deltaTotalStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ STRESS_TOTAL_DELTA ] = value + self._deltaTotalStress = value @property def rspReal( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ RSP_REAL ] + return self._rspReal @rspReal.setter def rspReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ RSP_REAL ] = value + self._rspReal = value @property def rspOed( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ RSP_OED ] + return self._rspOed @rspOed.setter def rspOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ RSP_OED ] = value + self._rspOed = value @property def effectiveStressRatioOed( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictBasicOutput[ STRESS_EFFECTIVE_RATIO_OED ] + return self._effectiveStressRatioOed @effectiveStressRatioOed.setter def effectiveStressRatioOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictBasicOutput[ STRESS_EFFECTIVE_RATIO_OED ] = value + self._effectiveStressRatioOed = value + + def getBasicOutputValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ]: + if name == BIOT_COEFFICIENT.attributeName: + return self.biotCoefficient + elif name == COMPRESSIBILITY.attributeName: + return self.compressibility + elif name == COMPRESSIBILITY_OED.attributeName: + return self.compressibilityOed + elif name == COMPRESSIBILITY_REAL.attributeName: + return self.compressibilityReal + elif name == SPECIFIC_GRAVITY.attributeName: + return self.specificGravity + elif name == STRESS_EFFECTIVE_RATIO_REAL.attributeName: + return self.effectiveStressRatioReal + elif name == STRESS_TOTAL.attributeName: + return self.totalStress + elif name == STRESS_TOTAL_T0.attributeName: + return self.totalStressT0 + elif name == STRESS_TOTAL_RATIO_REAL.attributeName: + return self.totalStressRatioReal + elif name == LITHOSTATIC_STRESS.attributeName: + return self.lithostaticStress + elif name == STRAIN_ELASTIC.attributeName: + return self.elasticStrain + elif name == STRESS_TOTAL_DELTA.attributeName: + return self.deltaTotalStress + elif name == RSP_REAL.attributeName: + return self.rspReal + elif name == RSP_OED.attributeName: + return self.rspOed + elif name == STRESS_EFFECTIVE_RATIO_OED.attributeName: + return self.effectiveStressRatioOed + else: + raise NameError - _basicOutput: BasicOutput = BasicOutput() + _basicOutput: BasicOutputValue = BasicOutputValue() @dataclass - class AdvancedOutput: - _dictAdvancedOutput: dict[ AttributeEnum, npt.NDArray[ np.float64 ] ] = field( default_factory=dict ) - - @property - def dictAdvancedOutput( self: Self ) -> dict[ AttributeEnum, npt.NDArray[ np.float64 ] ]: - return self._dictAdvancedOutput + class AdvancedOutputValue: + _criticalTotalStressRatio: npt.NDArray[ np.float64 ] = np.array( [] ) + _stressRatioThreshold: npt.NDArray[ np.float64 ] = np.array( [] ) + _criticalPorePressure: npt.NDArray[ np.float64 ] = np.array( [] ) + _criticalPorePressureIndex: npt.NDArray[ np.float64 ] = np.array( [] ) @property def criticalTotalStressRatio( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictAdvancedOutput[ CRITICAL_TOTAL_STRESS_RATIO ] + return self._criticalTotalStressRatio @criticalTotalStressRatio.setter def criticalTotalStressRatio( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictAdvancedOutput[ CRITICAL_TOTAL_STRESS_RATIO ] = value + self._criticalTotalStressRatio = value @property def stressRatioThreshold( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictAdvancedOutput[ TOTAL_STRESS_RATIO_THRESHOLD ] + return self._stressRatioThreshold @stressRatioThreshold.setter def stressRatioThreshold( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictAdvancedOutput[ TOTAL_STRESS_RATIO_THRESHOLD ] = value + self._stressRatioThreshold = value @property def criticalPorePressure( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictAdvancedOutput[ CRITICAL_PORE_PRESSURE ] + return self._criticalPorePressure @criticalPorePressure.setter def criticalPorePressure( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictAdvancedOutput[ CRITICAL_PORE_PRESSURE ] = value + self._criticalPorePressure = value @property def criticalPorePressureIndex( self: Self ) -> npt.NDArray[ np.float64 ]: - return self._dictAdvancedOutput[ CRITICAL_PORE_PRESSURE_THRESHOLD ] + return self._criticalPorePressureIndex @criticalPorePressureIndex.setter def criticalPorePressureIndex( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: - self._dictAdvancedOutput[ CRITICAL_PORE_PRESSURE_THRESHOLD ] = value + self._criticalPorePressureIndex = value + + def getAdvancedOutputValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ]: + if name == CRITICAL_TOTAL_STRESS_RATIO.attributeName: + return self.criticalTotalStressRatio + elif name == TOTAL_STRESS_RATIO_THRESHOLD.attributeName: + return self.stressRatioThreshold + elif name == CRITICAL_PORE_PRESSURE.attributeName: + return self.criticalPorePressure + elif name == CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName: + return self.criticalPorePressureIndex + else: + raise NameError - _advancedOutput: AdvancedOutput = AdvancedOutput() + _advancedOutput: AdvancedOutputValue = AdvancedOutputValue() def __init__( self: Self, @@ -510,50 +622,7 @@ def __init__( self.output.DeepCopy( mesh ) self.doComputeAdvancedOutputs: bool = computeAdvancedOutputs - - self._mandatoryAttributes._dictMandatoryAttribute = { - POROSITY: np.array( [] ), - POROSITY_T0: np.array( [] ), - PRESSURE: np.array( [] ), - DELTA_PRESSURE: np.array( [] ), - DENSITY: np.array( [] ), - STRESS_EFFECTIVE: np.array( [] ), - STRESS_EFFECTIVE_T0: np.array( [] ), - } - self._elasticModuli._dictElasticModuli = { - BULK_MODULUS: np.array( [] ), - SHEAR_MODULUS: np.array( [] ), - YOUNG_MODULUS: np.array( [] ), - POISSON_RATIO: np.array( [] ), - BULK_MODULUS_T0: np.array( [] ), - YOUNG_MODULUS_T0: np.array( [] ), - POISSON_RATIO_T0: np.array( [] ), - } - self._basicOutput._dictBasicOutput = { - BIOT_COEFFICIENT: np.array( [] ), - COMPRESSIBILITY: np.array( [] ), - COMPRESSIBILITY_OED: np.array( [] ), - COMPRESSIBILITY_REAL: np.array( [] ), - SPECIFIC_GRAVITY: np.array( [] ), - STRESS_EFFECTIVE_RATIO_REAL: np.array( [] ), - STRESS_TOTAL: np.array( [] ), - STRESS_TOTAL_T0: np.array( [] ), - STRESS_TOTAL_RATIO_REAL: np.array( [] ), - # LITHOSTATIC_STRESS: np.array( [] ), - STRAIN_ELASTIC: np.array( [] ), - STRESS_TOTAL_DELTA: np.array( [] ), - RSP_REAL: np.array( [] ), - RSP_OED: np.array( [] ), - STRESS_EFFECTIVE_RATIO_OED: np.array( [] ), - } - self._advancedOutput._dictAdvancedOutput = { - CRITICAL_TOTAL_STRESS_RATIO: np.array( [] ), - TOTAL_STRESS_RATIO_THRESHOLD: np.array( [] ), - CRITICAL_PORE_PRESSURE: np.array( [] ), - CRITICAL_PORE_PRESSURE_THRESHOLD: np.array( [] ), - } - - self._outputPresent: list[ str ] = [] + self._attributesToCreate: list[ AttributeEnum ] = [] # Logger. self.logger: Logger @@ -575,9 +644,34 @@ def applyFilter( self: Self ) -> bool: if not self.computeBasicOutputs(): return False - if self.doComputeAdvancedOutputs: - return self.computeAdvancedOutputs() + if self.doComputeAdvancedOutputs and not self.computeAdvancedOutputs(): + return False + + # Create an attribute on the mesh for each geomechanics outputs computed: + for attribute in self._attributesToCreate: + attributeName: str = attribute.attributeName + onPoints: bool = attribute.isOnPoints + array: npt.NDArray[ np.float64 ] + if attribute in ELASTIC_MODULI: + array = self._elasticModuli.getElasticModulusValue( attributeName ) + elif attribute in BASIC_OUTPUTS: + array = self._basicOutput.getBasicOutputValue( attributeName ) + elif attribute in ADVANCED_OUTPUTS: + array = self._advancedOutput.getAdvancedOutputValue( attributeName ) + componentNames: tuple[ str, ...] = () + if attribute.nbComponent == 6: + componentNames = ComponentNameEnum.XYZ.value + + if not createAttribute( self.output, + array, + attributeName, + componentNames=componentNames, + onPoints=onPoints, + logger=self.logger ): + return False + self.logger.info( "All the geomechanics properties have been add to the mesh." ) + self.logger.info( "The filter succeeded." ) return True def getOutput( self: Self ) -> Union[ vtkPointSet, vtkUnstructuredGrid ]: @@ -628,11 +722,13 @@ def _checkMandatoryAttributes( self: Self ) -> bool: Returns: bool: True if all needed attributes are present, False otherwise """ - for elasticModulus in self._elasticModuli.dictElasticModuli: + for elasticModulus in ELASTIC_MODULI: elasticModulusName: str = elasticModulus.attributeName elasticModulusOnPoints: bool = elasticModulus.isOnPoints if isAttributeInObject( self.output, elasticModulusName, elasticModulusOnPoints ): - self._elasticModuli._dictElasticModuli = ( elasticModulus, getArrayInObject( self.output, elasticModulusName, elasticModulusOnPoints ) ) + self._elasticModuli.setElasticModulusValue( + elasticModulus.attributeName, + getArrayInObject( self.output, elasticModulusName, elasticModulusOnPoints ) ) # Check the presence of the elastic moduli at the current time. self.computeYoungPoisson: bool @@ -640,8 +736,10 @@ def _checkMandatoryAttributes( self: Self ) -> bool: if self._elasticModuli.bulkModulus.size != 0 and self._elasticModuli.shearModulus.size != 0: self._elasticModuli.youngModulus = fcts.youngModulus( self._elasticModuli.bulkModulus, self._elasticModuli.shearModulus ) + self._attributesToCreate.append( YOUNG_MODULUS ) self._elasticModuli.poissonRatio = fcts.poissonRatio( self._elasticModuli.bulkModulus, self._elasticModuli.shearModulus ) + self._attributesToCreate.append( POISSON_RATIO ) self.computeYoungPoisson = True else: self.logger.error( @@ -652,8 +750,10 @@ def _checkMandatoryAttributes( self: Self ) -> bool: if self._elasticModuli.youngModulus.size != 0 and self._elasticModuli.poissonRatio.size != 0: self._elasticModuli.bulkModulus = fcts.bulkModulus( self._elasticModuli.youngModulus, self._elasticModuli.poissonRatio ) + self._attributesToCreate.append( BULK_MODULUS ) self._elasticModuli.shearModulus = fcts.shearModulus( self._elasticModuli.youngModulus, self._elasticModuli.poissonRatio ) + self._attributesToCreate.append( SHEAR_MODULUS ) self.computeYoungPoisson = False else: self.logger.error( @@ -671,6 +771,7 @@ def _checkMandatoryAttributes( self: Self ) -> bool: if self._elasticModuli.youngModulusT0.size != 0 and self._elasticModuli.poissonRatioT0.size != 0: self._elasticModuli.bulkModulusT0 = fcts.bulkModulus( self._elasticModuli.youngModulusT0, self._elasticModuli.poissonRatioT0 ) + self._attributesToCreate.append( BULK_MODULUS_T0 ) else: self.logger.error( f"{ BULK_MODULUS_T0.attributeName } or { YOUNG_MODULUS_T0.attributeName } and { POISSON_RATIO_T0.attributeName } are mandatory to compute geomechanical outputs." @@ -678,7 +779,7 @@ def _checkMandatoryAttributes( self: Self ) -> bool: return False # Check the presence of the other mandatory attributes - for mandatoryAttribute in self._mandatoryAttributes.dictMandatoryAttribute: + for mandatoryAttribute in MANDATORY_ATTRIBUTES: mandatoryAttributeName: str = mandatoryAttribute.attributeName mandatoryAttributeOnPoints: bool = mandatoryAttribute.isOnPoints if not isAttributeInObject( self.output, mandatoryAttributeName, mandatoryAttributeOnPoints ): @@ -686,7 +787,9 @@ def _checkMandatoryAttributes( self: Self ) -> bool: f"The mandatory attribute { mandatoryAttributeName } is missing to compute geomechanical outputs." ) return False else: - self._mandatoryAttributes.dictMandatoryAttribute = ( mandatoryAttribute, getArrayInObject( self.output, mandatoryAttributeName, mandatoryAttributeOnPoints ) ) + self._mandatoryAttributes.setMandatoryAttributeValue( + mandatoryAttributeName, + getArrayInObject( self.output, mandatoryAttributeName, mandatoryAttributeOnPoints ) ) return True @@ -734,16 +837,6 @@ def computeBasicOutputs( self: Self ) -> bool: if not self._computeReservoirStressPathReal(): return False - # Create an attribute on the mesh for each basic outputs - for basicOutput, array in self._basicOutput.dictBasicOutput.items(): - # Basic outputs with multiple components: - if basicOutput.attributeName in [ STRESS_TOTAL.attributeName, STRESS_TOTAL_T0.attributeName, STRESS_TOTAL_DELTA.attributeName, STRAIN_ELASTIC.attributeName, RSP_REAL.attributeName ]: - if not createAttribute( self.output, array, basicOutput.attributeName, componentNames=ComponentNameEnum.XYZ.value, onPoints=basicOutput.isOnPoints, logger=self.logger ): - return False - # Other basic outputs: - elif basicOutput.attributeName not in self._outputPresent and not createAttribute( self.output, array, basicOutput.attributeName, onPoints=basicOutput.isOnPoints, logger=self.logger ): - return False - self.logger.info( "All geomechanical basic outputs were successfully computed." ) return True @@ -759,11 +852,6 @@ def computeAdvancedOutputs( self: Self ) -> bool: if not self._computeCriticalPorePressure(): return False - # Create an attribute on the mesh for each advanced outputs - for advancedOutput, array in self._advancedOutput.dictAdvancedOutput.items(): - if advancedOutput not in self._outputPresent and not createAttribute( self.output, array, advancedOutput.attributeName, onPoints=advancedOutput.isOnPoints, logger=self.logger ): - return False - self.logger.info( "All geomechanical advanced outputs were successfully computed." ) return True @@ -776,8 +864,8 @@ def _computeBiotCoefficient( self: Self ) -> bool: if not isAttributeInObject( self.output, BIOT_COEFFICIENT.attributeName, BIOT_COEFFICIENT.isOnPoints ): self._basicOutput.biotCoefficient = fcts.biotCoefficient( self.physicalConstants.grainBulkModulus, self._elasticModuli.bulkModulus ) + self._attributesToCreate.append( BIOT_COEFFICIENT ) else: - self._outputPresent.append( BIOT_COEFFICIENT.attributeName ) self._basicOutput.biotCoefficient = getArrayInObject( self.output, BIOT_COEFFICIENT.attributeName, BIOT_COEFFICIENT.isOnPoints ) self.logger.warning( @@ -799,8 +887,8 @@ def _computeCompressibilityCoefficient( self: Self ) -> bool: self._elasticModuli.bulkModulus, self._basicOutput.biotCoefficient, self._mandatoryAttributes.porosity ) + self._attributesToCreate.append( COMPRESSIBILITY ) else: - self._outputPresent.append( COMPRESSIBILITY.attributeName ) self._basicOutput.compressibility = getArrayInObject( self.output, COMPRESSIBILITY.attributeName, COMPRESSIBILITY.isOnPoints ) self.logger.warning( @@ -809,12 +897,12 @@ def _computeCompressibilityCoefficient( self: Self ) -> bool: # oedometric compressibility if not isAttributeInObject( self.output, COMPRESSIBILITY_OED.attributeName, COMPRESSIBILITY_OED.isOnPoints ): self._basicOutput.compressibilityOed = fcts.compressibilityOed( self._elasticModuli.shearModulus, - self._elasticModuli.bulkModulus, - self._mandatoryAttributes.porosity ) + self._elasticModuli.bulkModulus, + self._mandatoryAttributes.porosity ) + self._attributesToCreate.append( COMPRESSIBILITY_OED ) else: - self._outputPresent.append( COMPRESSIBILITY_OED.attributeName ) self._basicOutput.compressibilityOed = getArrayInObject( self.output, COMPRESSIBILITY_OED.attributeName, - COMPRESSIBILITY_OED.isOnPoints ) + COMPRESSIBILITY_OED.isOnPoints ) self.logger.warning( f"{ COMPRESSIBILITY_OED.attributeName } is already on the mesh, it has not been computed by the filter." ) @@ -824,8 +912,8 @@ def _computeCompressibilityCoefficient( self: Self ) -> bool: self._basicOutput.compressibilityReal = fcts.compressibilityReal( self._mandatoryAttributes.deltaPressure, self._mandatoryAttributes.porosity, self._mandatoryAttributes.porosityInitial ) + self._attributesToCreate.append( COMPRESSIBILITY_REAL ) else: - self._outputPresent.append( COMPRESSIBILITY_REAL.attributeName ) self._basicOutput.compressibilityReal = getArrayInObject( self.output, COMPRESSIBILITY_REAL.attributeName, COMPRESSIBILITY_REAL.isOnPoints ) self.logger.warning( @@ -846,8 +934,8 @@ def _computeSpecificGravity( self: Self ) -> bool: if not isAttributeInObject( self.output, SPECIFIC_GRAVITY.attributeName, SPECIFIC_GRAVITY.isOnPoints ): self._basicOutput.specificGravity = fcts.specificGravity( self._mandatoryAttributes.density, self.physicalConstants.specificDensity ) + self._attributesToCreate.append( SPECIFIC_GRAVITY ) else: - self._outputPresent.append( SPECIFIC_GRAVITY.attributeName ) self._basicOutput.specificGravity = getArrayInObject( self.output, SPECIFIC_GRAVITY.attributeName, SPECIFIC_GRAVITY.isOnPoints ) self.logger.warning( @@ -869,8 +957,8 @@ def _doComputeStressRatioReal( self: Self, stress: npt.NDArray[ np.float64 ], stressRatioReal: npt.NDArray[ np.float64 ] if not isAttributeInObject( self.output, basicOutput.attributeName, basicOutput.isOnPoints ): stressRatioReal = fcts.stressRatio( horizontalStress, verticalStress ) + self._attributesToCreate.append( basicOutput ) else: - self._outputPresent.append( basicOutput.attributeName ) stressRatioReal = getArrayInObject( self.output, basicOutput.attributeName, basicOutput.isOnPoints ) self.logger.warning( f"{ basicOutput.attributeName } is already on the mesh, it has not been computed by the filter." ) @@ -937,8 +1025,8 @@ def _computeTotalStressInitial( self: Self ) -> bool: if not isAttributeInObject( self.output, STRESS_TOTAL_T0.attributeName, STRESS_TOTAL_T0.isOnPoints ): self._basicOutput.totalStressT0 = self._doComputeTotalStress( self._mandatoryAttributes.effectiveStressT0, pressureT0, biotCoefficientT0 ) + self._attributesToCreate.append( STRESS_TOTAL_T0 ) else: - self._outputPresent.append( STRESS_TOTAL_T0.attributeName ) self._basicOutput.totalStressT0 = getArrayInObject( self.output, STRESS_TOTAL_T0.attributeName, STRESS_TOTAL_T0.isOnPoints ) self.logger.warning( @@ -965,8 +1053,8 @@ def _computeTotalStresses( self: Self ) -> bool: self._basicOutput.totalStress = self._doComputeTotalStress( self._mandatoryAttributes.effectiveStress, self._mandatoryAttributes.pressure, self._basicOutput.biotCoefficient ) + self._attributesToCreate.append( STRESS_TOTAL ) else: - self._outputPresent.append( STRESS_TOTAL.attributeName ) self._basicOutput.totalStress = getArrayInObject( self.output, STRESS_TOTAL.attributeName, STRESS_TOTAL.isOnPoints ) self.logger.warning( @@ -989,8 +1077,8 @@ def computeLithostaticStress( self: Self ) -> bool: ) if LITHOSTATIC_STRESS.isOnPoints else self._doComputeDepthInMesh() self._basicOutput.lithostaticStress = fcts.lithostaticStress( depth, self._mandatoryAttributes.density, GRAVITY ) + self._attributesToCreate.append( LITHOSTATIC_STRESS ) else: - self._outputPresent.append( LITHOSTATIC_STRESS.attributeName ) self._basicOutput.lithostaticStress = getArrayInObject( self.output, LITHOSTATIC_STRESS.attributeName, LITHOSTATIC_STRESS.isOnPoints ) self.logger.warning( @@ -1079,8 +1167,8 @@ def _computeElasticStrain( self: Self ) -> bool: else: self._basicOutput.elasticStrain = fcts.elasticStrainFromYoungPoisson( deltaEffectiveStress, self._elasticModuli.youngModulus, self._elasticModuli.poissonRatio ) + self._attributesToCreate.append( STRAIN_ELASTIC ) else: - self._outputPresent.append( STRAIN_ELASTIC.attributeName ) self._basicOutput.totalStressT0 = getArrayInObject( self.output, STRAIN_ELASTIC.attributeName, STRAIN_ELASTIC.isOnPoints ) self.logger.warning( @@ -1097,8 +1185,8 @@ def _computeReservoirStressPathReal( self: Self ) -> bool: # create delta stress attribute for QC if not isAttributeInObject( self.output, STRESS_TOTAL_DELTA.attributeName, STRESS_TOTAL_DELTA.isOnPoints ): self._basicOutput.deltaTotalStress = self._basicOutput.totalStress - self._basicOutput.totalStressT0 + self._attributesToCreate.append( STRESS_TOTAL_DELTA ) else: - self._outputPresent.append( STRESS_TOTAL_DELTA.attributeName ) self._basicOutput.deltaTotalStress = getArrayInObject( self.output, STRESS_TOTAL_DELTA.attributeName, STRESS_TOTAL_DELTA.isOnPoints ) self.logger.warning( @@ -1108,8 +1196,8 @@ def _computeReservoirStressPathReal( self: Self ) -> bool: if not isAttributeInObject( self.output, RSP_REAL.attributeName, RSP_REAL.isOnPoints ): self._basicOutput.rspReal = fcts.reservoirStressPathReal( self._basicOutput.deltaTotalStress, self._mandatoryAttributes.deltaPressure ) + self._attributesToCreate.append( RSP_REAL ) else: - self._outputPresent.append( RSP_REAL.attributeName ) self._basicOutput.rspReal = getArrayInObject( self.output, RSP_REAL.attributeName, RSP_REAL.isOnPoints ) self.logger.warning( f"{ RSP_REAL.attributeName } is already on the mesh, it has not been computed by the filter." ) @@ -1125,8 +1213,8 @@ def _computeReservoirStressPathOed( self: Self ) -> bool: if not isAttributeInObject( self.output, RSP_OED.attributeName, RSP_OED.isOnPoints ): self._basicOutput.rspOed = fcts.reservoirStressPathOed( self._basicOutput.biotCoefficient, self._elasticModuli.poissonRatio ) + self._attributesToCreate.append( RSP_OED ) else: - self._outputPresent.append( RSP_OED.attributeName ) self._basicOutput.rspOed = getArrayInObject( self.output, RSP_OED.attributeName, RSP_OED.isOnPoints ) self.logger.warning( f"{ RSP_OED.attributeName } is already on the mesh, it has not been computed by the filter." ) @@ -1142,8 +1230,8 @@ def _computeEffectiveStressRatioOed( self: Self ) -> bool: if not isAttributeInObject( self.output, STRESS_EFFECTIVE_RATIO_OED.attributeName, STRESS_EFFECTIVE_RATIO_OED.isOnPoints ): self._basicOutput.effectiveStressRatioOed = fcts.deviatoricStressPathOed( self._elasticModuli.poissonRatio ) + self._attributesToCreate.append( STRESS_EFFECTIVE_RATIO_OED ) else: - self._outputPresent.append( STRESS_EFFECTIVE_RATIO_OED.attributeName ) self._basicOutput.effectiveStressRatioOed = getArrayInObject( self.output, STRESS_EFFECTIVE_RATIO_OED.attributeName, STRESS_EFFECTIVE_RATIO_OED.isOnPoints ) @@ -1164,8 +1252,8 @@ def _computeCriticalTotalStressRatio( self: Self ) -> bool: verticalStress: npt.NDArray[ np.float64 ] = self._basicOutput.totalStress[ :, 2 ] self._advancedOutput.criticalTotalStressRatio = fcts.criticalTotalStressRatio( self._mandatoryAttributes.pressure, verticalStress ) + self._attributesToCreate.append( CRITICAL_TOTAL_STRESS_RATIO ) else: - self._outputPresent.append( CRITICAL_TOTAL_STRESS_RATIO.attributeName ) self._advancedOutput.criticalTotalStressRatio = getArrayInObject( self.output, CRITICAL_TOTAL_STRESS_RATIO.attributeName, CRITICAL_TOTAL_STRESS_RATIO.isOnPoints ) @@ -1180,8 +1268,8 @@ def _computeCriticalTotalStressRatio( self: Self ) -> bool: np.arange( self._basicOutput.totalStress[ :, :2 ].shape[ 0 ] ), mask ] self._advancedOutput.stressRatioThreshold = fcts.totalStressRatioThreshold( self._mandatoryAttributes.pressure, horizontalStress ) + self._attributesToCreate.append( TOTAL_STRESS_RATIO_THRESHOLD ) else: - self._outputPresent.append( TOTAL_STRESS_RATIO_THRESHOLD.attributeName ) self._advancedOutput.stressRatioThreshold = getArrayInObject( self.output, TOTAL_STRESS_RATIO_THRESHOLD.attributeName, TOTAL_STRESS_RATIO_THRESHOLD.isOnPoints ) @@ -1202,8 +1290,8 @@ def _computeCriticalPorePressure( self: Self ) -> bool: self._advancedOutput.criticalPorePressure = fcts.criticalPorePressure( -1.0 * self._basicOutput.totalStress, self.physicalConstants.rockCohesion, self.physicalConstants.frictionAngle ) + self._attributesToCreate.append( CRITICAL_PORE_PRESSURE ) else: - self._outputPresent.append( CRITICAL_PORE_PRESSURE.attributeName ) self._advancedOutput.criticalPorePressure = getArrayInObject( self.output, CRITICAL_PORE_PRESSURE.attributeName, CRITICAL_PORE_PRESSURE.isOnPoints ) @@ -1216,8 +1304,8 @@ def _computeCriticalPorePressure( self: Self ) -> bool: CRITICAL_PORE_PRESSURE_THRESHOLD.isOnPoints ): self._advancedOutput.criticalPorePressureIndex = fcts.criticalPorePressureThreshold( self._mandatoryAttributes.pressure, self._advancedOutput.criticalPorePressure ) + self._attributesToCreate.append( CRITICAL_PORE_PRESSURE_THRESHOLD ) else: - self._outputPresent.append( CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName ) self._advancedOutput.criticalPorePressureIndex = getArrayInObject( self.output, CRITICAL_PORE_PRESSURE_THRESHOLD.attributeName, CRITICAL_PORE_PRESSURE_THRESHOLD.isOnPoints ) From f95b50c8f48e1e6c87f2c1e7e3584c0c35103bb0 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Tue, 14 Oct 2025 16:44:15 +0200 Subject: [PATCH 37/40] fix ruff --- geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py index 2b2aa1e29..b077fe7b5 100644 --- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py +++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolume.py @@ -333,7 +333,7 @@ def RequestData( # 1. extract volume self.doExtractAndMerge() # 2. compute Geomechanical outputs in volume mesh - a = self.computeAdditionalOutputsVolume() + self.computeAdditionalOutputsVolume() except AssertionError as e: mess: str = "Geomechanics workflow failed due to:" From 1bb88c67fcaf4db929084184dd414b9f443322cc Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Wed, 15 Oct 2025 15:48:18 +0200 Subject: [PATCH 38/40] update the doc --- .../post_processing/GeomechanicsCalculator.py | 95 +++++++++++++++++-- 1 file changed, 87 insertions(+), 8 deletions(-) diff --git a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py index 3dbe8719d..3cf0bb32d 100644 --- a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py +++ b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py @@ -52,6 +52,7 @@ - The pressure named "pressure" The basic geomechanics outputs are: + - The elastic moduli not present on the mesh - Biot coefficient - Compressibility, oedometric compressibility and real compressibility coefficient - Specific gravity @@ -59,7 +60,7 @@ - Total initial stress, total current stress and total stress ratio - Lithostatic stress (physic to update) - Elastic stain - - Reservoir stress path and reservoir stress path in oedometric condition + - Real reservoir stress path and reservoir stress path in oedometric condition The advanced geomechanics outputs are: - Fracture index and threshold @@ -68,8 +69,8 @@ GeomechanicsCalculator filter input mesh is either vtkPointSet or vtkUnstructuredGrid and returned mesh is of same type as input. - .. Important:: - Please refer to the GeosExtractMergeBlockVolume* filters to provide the correct input. +.. Important:: + Please refer to the GeosExtractMergeBlockVolume* filters to provide the correct input. .. Note:: - The default physical constants used by the filter are: @@ -101,20 +102,20 @@ ## Basic outputs grainBulkModulus: float specificDensity: float - filter.SetGrainBulkModulus(grainBulkModulus) - filter.SetSpecificDensity(specificDensity) + filter.physicalConstants.grainBulkModulus = grainBulkModulus + filter.physicalConstants.specificDensity = specificDensity ## Advanced outputs rockCohesion: float frictionAngle: float - filter.SetRockCohesion(rockCohesion) - filter.SetFrictionAngle(frictionAngle) + filter.physicalConstants.rockCohesion = rockCohesion + filter.physicalConstants.frictionAngle = frictionAngle # Do calculations filter.applyFilter() # Get the mesh with the geomechanical output as attribute - output :Union[vtkPointSet, vtkUnstructuredGrid] + output: Union[vtkPointSet, vtkUnstructuredGrid] output = filter.getOutput() """ @@ -177,6 +178,7 @@ class GeomechanicsCalculator: @dataclass class PhysicalConstants: + """The dataclass with the value of the physical constant used to compute geomechanics properties.""" ## Basic outputs _grainBulkModulus: float = DEFAULT_GRAIN_BULK_MODULUS _specificDensity: float = WATER_DENSITY @@ -186,6 +188,7 @@ class PhysicalConstants: @property def grainBulkModulus( self: Self ) -> float: + """Get the grain bulk modulus value.""" return self._grainBulkModulus @grainBulkModulus.setter @@ -194,6 +197,7 @@ def grainBulkModulus( self: Self, value: float ) -> None: @property def specificDensity( self: Self ) -> float: + """Get the specific density value.""" return self._specificDensity @specificDensity.setter @@ -202,6 +206,7 @@ def specificDensity( self: Self, value: float ) -> None: @property def rockCohesion( self: Self ) -> float: + """Get the rock cohesion value.""" return self._rockCohesion @rockCohesion.setter @@ -210,6 +215,7 @@ def rockCohesion( self: Self, value: float ) -> None: @property def frictionAngle( self: Self ) -> float: + """Get the friction angle value.""" return self._frictionAngle @frictionAngle.setter @@ -220,6 +226,7 @@ def frictionAngle( self: Self, value: float ) -> None: @dataclass class ElasticModuliValue: + """The dataclass with the value of the elastic moduli.""" _bulkModulus: npt.NDArray[ np.float64 ] = np.array( [] ) _shearModulus: npt.NDArray[ np.float64 ] = np.array( [] ) _youngModulus: npt.NDArray[ np.float64 ] = np.array( [] ) @@ -230,6 +237,7 @@ class ElasticModuliValue: @property def bulkModulus( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the bulk modulus value.""" return self._bulkModulus @bulkModulus.setter @@ -238,6 +246,7 @@ def bulkModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def shearModulus( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the shear modulus value.""" return self._shearModulus @shearModulus.setter @@ -246,6 +255,7 @@ def shearModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def youngModulus( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the young modulus value.""" return self._youngModulus @youngModulus.setter @@ -254,6 +264,7 @@ def youngModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def poissonRatio( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the poisson ratio value.""" return self._poissonRatio @poissonRatio.setter @@ -262,6 +273,7 @@ def poissonRatio( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def bulkModulusT0( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the bulk modulus at the initial time value.""" return self._bulkModulusT0 @bulkModulusT0.setter @@ -270,6 +282,7 @@ def bulkModulusT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def youngModulusT0( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the young modulus at the initial time value.""" return self._youngModulusT0 @youngModulusT0.setter @@ -278,6 +291,7 @@ def youngModulusT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def poissonRatioT0( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the poisson ration at the initial time value.""" return self._poissonRatioT0 @poissonRatioT0.setter @@ -285,6 +299,12 @@ def poissonRatioT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._poissonRatioT0 = value def setElasticModulusValue( self: Self, name: str, value: npt.NDArray[ np.float64 ] ) -> None: + """Set the elastic modulus value wanted. + + Args: + name (str): The name of the elastic modulus. + value (npt.NDArray[np.float64]): The value to set. + """ if name == BULK_MODULUS.attributeName: self.bulkModulus = value elif name == BULK_MODULUS_T0.attributeName: @@ -301,6 +321,14 @@ def setElasticModulusValue( self: Self, name: str, value: npt.NDArray[ np.float6 self.poissonRatioT0 = value def getElasticModulusValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ]: + """Get the wanted elastic modulus value. + + Args: + name (str): The name of the wanted elastic modulus. + + Returns: + npt.NDArray[np.float64]: The value of the elastic modulus. + """ if name == BULK_MODULUS.attributeName: return self.bulkModulus elif name == BULK_MODULUS_T0.attributeName: @@ -322,6 +350,7 @@ def getElasticModulusValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ] @dataclass class MandatoryAttributesValue: + """The dataclass with the value of mandatory properties to have to compute other geomechanics properties.""" _porosity: npt.NDArray[ np.float64 ] = np.array( [] ) _porosityInitial: npt.NDArray[ np.float64 ] = np.array( [] ) _pressure: npt.NDArray[ np.float64 ] = np.array( [] ) @@ -332,33 +361,46 @@ class MandatoryAttributesValue: @property def porosity( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the porosity value.""" return self._porosity @property def porosityInitial( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the initial porosity value.""" return self._porosityInitial @property def pressure( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the pressure value.""" return self._pressure @property def deltaPressure( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the delta pressure value.""" return self._deltaPressure @property def density( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the density value.""" return self._density @property def effectiveStress( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the effective stress value.""" return self._effectiveStress @property def effectiveStressT0( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the initial effective stress value.""" return self._effectiveStressT0 def setMandatoryAttributeValue( self: Self, name: str, value: npt.NDArray[ np.float64 ] ) -> None: + """Set the value of a mandatory property. + + Args: + name (str): The name of the property. + value (npt.NDArray[np.float64]): The value to set. + """ if name == POROSITY.attributeName: self._porosity = value elif name == POROSITY_T0.attributeName: @@ -378,6 +420,7 @@ def setMandatoryAttributeValue( self: Self, name: str, value: npt.NDArray[ np.fl @dataclass class BasicOutputValue: + """The dataclass with the value of the basic geomechanics outputs.""" _biotCoefficient: npt.NDArray[ np.float64 ] = np.array( [] ) _compressibility: npt.NDArray[ np.float64 ] = np.array( [] ) _compressibilityOed: npt.NDArray[ np.float64 ] = np.array( [] ) @@ -396,6 +439,7 @@ class BasicOutputValue: @property def biotCoefficient( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the biot coefficient value.""" return self._biotCoefficient @biotCoefficient.setter @@ -404,6 +448,7 @@ def biotCoefficient( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def compressibility( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the compressibility value.""" return self._compressibility @compressibility.setter @@ -412,6 +457,7 @@ def compressibility( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def compressibilityOed( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the compressibility in oedometric condition value.""" return self._compressibilityOed @compressibilityOed.setter @@ -420,6 +466,7 @@ def compressibilityOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def compressibilityReal( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the real compressibility value.""" return self._compressibilityReal @compressibilityReal.setter @@ -428,6 +475,7 @@ def compressibilityReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def specificGravity( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the specific gravity value.""" return self._specificGravity @specificGravity.setter @@ -436,6 +484,7 @@ def specificGravity( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def effectiveStressRatioReal( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the real effective stress ratio value.""" return self._effectiveStressRatioReal @effectiveStressRatioReal.setter @@ -444,6 +493,7 @@ def effectiveStressRatioReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> @property def totalStress( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the total stress value.""" return self._totalStress @totalStress.setter @@ -452,6 +502,7 @@ def totalStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def totalStressT0( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the initial total stress value.""" return self._totalStressT0 @totalStressT0.setter @@ -460,6 +511,7 @@ def totalStressT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def totalStressRatioReal( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the total real stress ratio value.""" return self._totalStressRatioReal @totalStressRatioReal.setter @@ -468,6 +520,7 @@ def totalStressRatioReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None @property def lithostaticStress( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the lithostatic stress value.""" return self._lithostaticStress @lithostaticStress.setter @@ -476,6 +529,7 @@ def lithostaticStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def elasticStrain( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the elastic strain value.""" return self._elasticStrain @elasticStrain.setter @@ -484,6 +538,7 @@ def elasticStrain( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def deltaTotalStress( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the total delta stress value.""" return self._deltaTotalStress @deltaTotalStress.setter @@ -492,6 +547,7 @@ def deltaTotalStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def rspReal( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the real reservoir stress path value.""" return self._rspReal @rspReal.setter @@ -500,6 +556,7 @@ def rspReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def rspOed( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the reservoir stress path in oedometric condition value.""" return self._rspOed @rspOed.setter @@ -508,6 +565,7 @@ def rspOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: @property def effectiveStressRatioOed( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the effective stress ratio oedometric value.""" return self._effectiveStressRatioOed @effectiveStressRatioOed.setter @@ -515,6 +573,14 @@ def effectiveStressRatioOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> N self._effectiveStressRatioOed = value def getBasicOutputValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ]: + """Get the value of the basic output wanted. + + Args: + name (str): The name of the basic output. + + Returns: + npt.NDArray[ np.float64 ]: the value of the basic output. + """ if name == BIOT_COEFFICIENT.attributeName: return self.biotCoefficient elif name == COMPRESSIBILITY.attributeName: @@ -552,6 +618,7 @@ def getBasicOutputValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ]: @dataclass class AdvancedOutputValue: + """The dataclass with the value of the advanced geomechanics outputs.""" _criticalTotalStressRatio: npt.NDArray[ np.float64 ] = np.array( [] ) _stressRatioThreshold: npt.NDArray[ np.float64 ] = np.array( [] ) _criticalPorePressure: npt.NDArray[ np.float64 ] = np.array( [] ) @@ -559,6 +626,7 @@ class AdvancedOutputValue: @property def criticalTotalStressRatio( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the critical total stress ratio value.""" return self._criticalTotalStressRatio @criticalTotalStressRatio.setter @@ -567,6 +635,7 @@ def criticalTotalStressRatio( self: Self, value: npt.NDArray[ np.float64 ] ) -> @property def stressRatioThreshold( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the stress ratio threshold value.""" return self._stressRatioThreshold @stressRatioThreshold.setter @@ -575,6 +644,7 @@ def stressRatioThreshold( self: Self, value: npt.NDArray[ np.float64 ] ) -> None @property def criticalPorePressure( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the critical pore pressure value.""" return self._criticalPorePressure @criticalPorePressure.setter @@ -583,6 +653,7 @@ def criticalPorePressure( self: Self, value: npt.NDArray[ np.float64 ] ) -> None @property def criticalPorePressureIndex( self: Self ) -> npt.NDArray[ np.float64 ]: + """Get the critical pore pressure index value.""" return self._criticalPorePressureIndex @criticalPorePressureIndex.setter @@ -590,6 +661,14 @@ def criticalPorePressureIndex( self: Self, value: npt.NDArray[ np.float64 ] ) -> self._criticalPorePressureIndex = value def getAdvancedOutputValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ]: + """Get the value of the advanced output wanted. + + Args: + name (str): The name of the advanced output. + + Returns: + npt.NDArray[ np.float64 ]: the value of the advanced output. + """ if name == CRITICAL_TOTAL_STRESS_RATIO.attributeName: return self.criticalTotalStressRatio elif name == TOTAL_STRESS_RATIO_THRESHOLD.attributeName: From 452d5ccb8362fc893b8b4d70e403bc4196ed77b1 Mon Sep 17 00:00:00 2001 From: Romain Baville <126683264+RomainBaville@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:06:20 +0200 Subject: [PATCH 39/40] Apply suggestions from Paloma's review Co-authored-by: paloma-martinez <104762252+paloma-martinez@users.noreply.github.com> --- .../geos/processing/post_processing/GeomechanicsCalculator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py index 3cf0bb32d..0e78fdd8c 100644 --- a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py +++ b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py @@ -749,7 +749,7 @@ def applyFilter( self: Self ) -> bool: logger=self.logger ): return False - self.logger.info( "All the geomechanics properties have been add to the mesh." ) + self.logger.info( "All the geomechanics properties have been added to the mesh." ) self.logger.info( "The filter succeeded." ) return True From b40cb808696324835c3f262ffc4b42e01c21d0e2 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Mon, 20 Oct 2025 12:05:35 +0200 Subject: [PATCH 40/40] Fix dataclass default value --- .../post_processing/GeomechanicsCalculator.py | 325 +++++++++++------- .../src/geos/utils/GeosOutputsConstants.py | 12 +- 2 files changed, 198 insertions(+), 139 deletions(-) diff --git a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py index 0e78fdd8c..ecef2cfc5 100644 --- a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py +++ b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py @@ -222,21 +222,19 @@ def frictionAngle( self: Self ) -> float: def frictionAngle( self: Self, value: float ) -> None: self._frictionAngle = value - physicalConstants: PhysicalConstants = PhysicalConstants() - @dataclass class ElasticModuliValue: """The dataclass with the value of the elastic moduli.""" - _bulkModulus: npt.NDArray[ np.float64 ] = np.array( [] ) - _shearModulus: npt.NDArray[ np.float64 ] = np.array( [] ) - _youngModulus: npt.NDArray[ np.float64 ] = np.array( [] ) - _poissonRatio: npt.NDArray[ np.float64 ] = np.array( [] ) - _bulkModulusT0: npt.NDArray[ np.float64 ] = np.array( [] ) - _youngModulusT0: npt.NDArray[ np.float64 ] = np.array( [] ) - _poissonRatioT0: npt.NDArray[ np.float64 ] = np.array( [] ) + _bulkModulus: npt.NDArray[ np.float64 ] | None = None + _shearModulus: npt.NDArray[ np.float64 ] | None = None + _youngModulus: npt.NDArray[ np.float64 ] | None = None + _poissonRatio: npt.NDArray[ np.float64 ] | None = None + _bulkModulusT0: npt.NDArray[ np.float64 ] | None = None + _youngModulusT0: npt.NDArray[ np.float64 ] | None = None + _poissonRatioT0: npt.NDArray[ np.float64 ] | None = None @property - def bulkModulus( self: Self ) -> npt.NDArray[ np.float64 ]: + def bulkModulus( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the bulk modulus value.""" return self._bulkModulus @@ -245,7 +243,7 @@ def bulkModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._bulkModulus = value @property - def shearModulus( self: Self ) -> npt.NDArray[ np.float64 ]: + def shearModulus( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the shear modulus value.""" return self._shearModulus @@ -254,7 +252,7 @@ def shearModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._shearModulus = value @property - def youngModulus( self: Self ) -> npt.NDArray[ np.float64 ]: + def youngModulus( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the young modulus value.""" return self._youngModulus @@ -263,7 +261,7 @@ def youngModulus( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._youngModulus = value @property - def poissonRatio( self: Self ) -> npt.NDArray[ np.float64 ]: + def poissonRatio( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the poisson ratio value.""" return self._poissonRatio @@ -272,7 +270,7 @@ def poissonRatio( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._poissonRatio = value @property - def bulkModulusT0( self: Self ) -> npt.NDArray[ np.float64 ]: + def bulkModulusT0( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the bulk modulus at the initial time value.""" return self._bulkModulusT0 @@ -281,7 +279,7 @@ def bulkModulusT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._bulkModulusT0 = value @property - def youngModulusT0( self: Self ) -> npt.NDArray[ np.float64 ]: + def youngModulusT0( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the young modulus at the initial time value.""" return self._youngModulusT0 @@ -290,7 +288,7 @@ def youngModulusT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._youngModulusT0 = value @property - def poissonRatioT0( self: Self ) -> npt.NDArray[ np.float64 ]: + def poissonRatioT0( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the poisson ration at the initial time value.""" return self._poissonRatioT0 @@ -320,7 +318,7 @@ def setElasticModulusValue( self: Self, name: str, value: npt.NDArray[ np.float6 elif name == POISSON_RATIO_T0.attributeName: self.poissonRatioT0 = value - def getElasticModulusValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ]: + def getElasticModulusValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ] | None: """Get the wanted elastic modulus value. Args: @@ -346,51 +344,49 @@ def getElasticModulusValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ] else: raise NameError - _elasticModuli: ElasticModuliValue = ElasticModuliValue() - @dataclass class MandatoryAttributesValue: """The dataclass with the value of mandatory properties to have to compute other geomechanics properties.""" - _porosity: npt.NDArray[ np.float64 ] = np.array( [] ) - _porosityInitial: npt.NDArray[ np.float64 ] = np.array( [] ) - _pressure: npt.NDArray[ np.float64 ] = np.array( [] ) - _deltaPressure: npt.NDArray[ np.float64 ] = np.array( [] ) - _density: npt.NDArray[ np.float64 ] = np.array( [] ) - _effectiveStress: npt.NDArray[ np.float64 ] = np.array( [] ) - _effectiveStressT0: npt.NDArray[ np.float64 ] = np.array( [] ) + _porosity: npt.NDArray[ np.float64 ] | None = None + _porosityInitial: npt.NDArray[ np.float64 ] | None = None + _pressure: npt.NDArray[ np.float64 ] | None = None + _deltaPressure: npt.NDArray[ np.float64 ] | None = None + _density: npt.NDArray[ np.float64 ] | None = None + _effectiveStress: npt.NDArray[ np.float64 ] | None = None + _effectiveStressT0: npt.NDArray[ np.float64 ] | None = None @property - def porosity( self: Self ) -> npt.NDArray[ np.float64 ]: + def porosity( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the porosity value.""" return self._porosity @property - def porosityInitial( self: Self ) -> npt.NDArray[ np.float64 ]: + def porosityInitial( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the initial porosity value.""" return self._porosityInitial @property - def pressure( self: Self ) -> npt.NDArray[ np.float64 ]: + def pressure( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the pressure value.""" return self._pressure @property - def deltaPressure( self: Self ) -> npt.NDArray[ np.float64 ]: + def deltaPressure( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the delta pressure value.""" return self._deltaPressure @property - def density( self: Self ) -> npt.NDArray[ np.float64 ]: + def density( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the density value.""" return self._density @property - def effectiveStress( self: Self ) -> npt.NDArray[ np.float64 ]: + def effectiveStress( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the effective stress value.""" return self._effectiveStress @property - def effectiveStressT0( self: Self ) -> npt.NDArray[ np.float64 ]: + def effectiveStressT0( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the initial effective stress value.""" return self._effectiveStressT0 @@ -416,29 +412,27 @@ def setMandatoryAttributeValue( self: Self, name: str, value: npt.NDArray[ np.fl elif name == STRESS_EFFECTIVE_T0.attributeName: self._effectiveStressT0 = value - _mandatoryAttributes: MandatoryAttributesValue = MandatoryAttributesValue() - @dataclass class BasicOutputValue: """The dataclass with the value of the basic geomechanics outputs.""" - _biotCoefficient: npt.NDArray[ np.float64 ] = np.array( [] ) - _compressibility: npt.NDArray[ np.float64 ] = np.array( [] ) - _compressibilityOed: npt.NDArray[ np.float64 ] = np.array( [] ) - _compressibilityReal: npt.NDArray[ np.float64 ] = np.array( [] ) - _specificGravity: npt.NDArray[ np.float64 ] = np.array( [] ) - _effectiveStressRatioReal: npt.NDArray[ np.float64 ] = np.array( [] ) - _totalStress: npt.NDArray[ np.float64 ] = np.array( [] ) - _totalStressT0: npt.NDArray[ np.float64 ] = np.array( [] ) - _totalStressRatioReal: npt.NDArray[ np.float64 ] = np.array( [] ) - # _lithostaticStress: npt.NDArray[ np.float64 ] = np.array( [] ) - _elasticStrain: npt.NDArray[ np.float64 ] = np.array( [] ) - _deltaTotalStress: npt.NDArray[ np.float64 ] = np.array( [] ) - _rspReal: npt.NDArray[ np.float64 ] = np.array( [] ) - _rspOed: npt.NDArray[ np.float64 ] = np.array( [] ) - _effectiveStressRatioOed: npt.NDArray[ np.float64 ] = np.array( [] ) + _biotCoefficient: npt.NDArray[ np.float64 ] | None = None + _compressibility: npt.NDArray[ np.float64 ] | None = None + _compressibilityOed: npt.NDArray[ np.float64 ] | None = None + _compressibilityReal: npt.NDArray[ np.float64 ] | None = None + _specificGravity: npt.NDArray[ np.float64 ] | None = None + _effectiveStressRatioReal: npt.NDArray[ np.float64 ] | None = None + _totalStress: npt.NDArray[ np.float64 ] | None = None + _totalStressT0: npt.NDArray[ np.float64 ] | None = None + _totalStressRatioReal: npt.NDArray[ np.float64 ] | None = None + # _lithostaticStress: npt.NDArray[ np.float64 ] | None = None + _elasticStrain: npt.NDArray[ np.float64 ] | None = None + _deltaTotalStress: npt.NDArray[ np.float64 ] | None = None + _rspReal: npt.NDArray[ np.float64 ] | None = None + _rspOed: npt.NDArray[ np.float64 ] | None = None + _effectiveStressRatioOed: npt.NDArray[ np.float64 ] | None = None @property - def biotCoefficient( self: Self ) -> npt.NDArray[ np.float64 ]: + def biotCoefficient( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the biot coefficient value.""" return self._biotCoefficient @@ -447,7 +441,7 @@ def biotCoefficient( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._biotCoefficient = value @property - def compressibility( self: Self ) -> npt.NDArray[ np.float64 ]: + def compressibility( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the compressibility value.""" return self._compressibility @@ -456,7 +450,7 @@ def compressibility( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._compressibility = value @property - def compressibilityOed( self: Self ) -> npt.NDArray[ np.float64 ]: + def compressibilityOed( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the compressibility in oedometric condition value.""" return self._compressibilityOed @@ -465,7 +459,7 @@ def compressibilityOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._compressibilityOed = value @property - def compressibilityReal( self: Self ) -> npt.NDArray[ np.float64 ]: + def compressibilityReal( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the real compressibility value.""" return self._compressibilityReal @@ -474,7 +468,7 @@ def compressibilityReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._compressibilityReal = value @property - def specificGravity( self: Self ) -> npt.NDArray[ np.float64 ]: + def specificGravity( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the specific gravity value.""" return self._specificGravity @@ -483,7 +477,7 @@ def specificGravity( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._specificGravity = value @property - def effectiveStressRatioReal( self: Self ) -> npt.NDArray[ np.float64 ]: + def effectiveStressRatioReal( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the real effective stress ratio value.""" return self._effectiveStressRatioReal @@ -492,7 +486,7 @@ def effectiveStressRatioReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> self._effectiveStressRatioReal = value @property - def totalStress( self: Self ) -> npt.NDArray[ np.float64 ]: + def totalStress( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the total stress value.""" return self._totalStress @@ -501,7 +495,7 @@ def totalStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._totalStress = value @property - def totalStressT0( self: Self ) -> npt.NDArray[ np.float64 ]: + def totalStressT0( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the initial total stress value.""" return self._totalStressT0 @@ -510,7 +504,7 @@ def totalStressT0( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._totalStressT0 = value @property - def totalStressRatioReal( self: Self ) -> npt.NDArray[ np.float64 ]: + def totalStressRatioReal( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the total real stress ratio value.""" return self._totalStressRatioReal @@ -519,7 +513,7 @@ def totalStressRatioReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None self._totalStressRatioReal = value @property - def lithostaticStress( self: Self ) -> npt.NDArray[ np.float64 ]: + def lithostaticStress( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the lithostatic stress value.""" return self._lithostaticStress @@ -528,7 +522,7 @@ def lithostaticStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._lithostaticStress = value @property - def elasticStrain( self: Self ) -> npt.NDArray[ np.float64 ]: + def elasticStrain( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the elastic strain value.""" return self._elasticStrain @@ -537,7 +531,7 @@ def elasticStrain( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._elasticStrain = value @property - def deltaTotalStress( self: Self ) -> npt.NDArray[ np.float64 ]: + def deltaTotalStress( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the total delta stress value.""" return self._deltaTotalStress @@ -546,7 +540,7 @@ def deltaTotalStress( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._deltaTotalStress = value @property - def rspReal( self: Self ) -> npt.NDArray[ np.float64 ]: + def rspReal( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the real reservoir stress path value.""" return self._rspReal @@ -555,7 +549,7 @@ def rspReal( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._rspReal = value @property - def rspOed( self: Self ) -> npt.NDArray[ np.float64 ]: + def rspOed( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the reservoir stress path in oedometric condition value.""" return self._rspOed @@ -564,7 +558,7 @@ def rspOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._rspOed = value @property - def effectiveStressRatioOed( self: Self ) -> npt.NDArray[ np.float64 ]: + def effectiveStressRatioOed( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the effective stress ratio oedometric value.""" return self._effectiveStressRatioOed @@ -572,7 +566,7 @@ def effectiveStressRatioOed( self: Self ) -> npt.NDArray[ np.float64 ]: def effectiveStressRatioOed( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._effectiveStressRatioOed = value - def getBasicOutputValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ]: + def getBasicOutputValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ] | None: """Get the value of the basic output wanted. Args: @@ -614,18 +608,16 @@ def getBasicOutputValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ]: else: raise NameError - _basicOutput: BasicOutputValue = BasicOutputValue() - @dataclass class AdvancedOutputValue: """The dataclass with the value of the advanced geomechanics outputs.""" - _criticalTotalStressRatio: npt.NDArray[ np.float64 ] = np.array( [] ) - _stressRatioThreshold: npt.NDArray[ np.float64 ] = np.array( [] ) - _criticalPorePressure: npt.NDArray[ np.float64 ] = np.array( [] ) - _criticalPorePressureIndex: npt.NDArray[ np.float64 ] = np.array( [] ) + _criticalTotalStressRatio: npt.NDArray[ np.float64 ] | None = None + _stressRatioThreshold: npt.NDArray[ np.float64 ] | None = None + _criticalPorePressure: npt.NDArray[ np.float64 ] | None = None + _criticalPorePressureIndex: npt.NDArray[ np.float64 ] | None = None @property - def criticalTotalStressRatio( self: Self ) -> npt.NDArray[ np.float64 ]: + def criticalTotalStressRatio( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the critical total stress ratio value.""" return self._criticalTotalStressRatio @@ -634,7 +626,7 @@ def criticalTotalStressRatio( self: Self, value: npt.NDArray[ np.float64 ] ) -> self._criticalTotalStressRatio = value @property - def stressRatioThreshold( self: Self ) -> npt.NDArray[ np.float64 ]: + def stressRatioThreshold( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the stress ratio threshold value.""" return self._stressRatioThreshold @@ -643,7 +635,7 @@ def stressRatioThreshold( self: Self, value: npt.NDArray[ np.float64 ] ) -> None self._stressRatioThreshold = value @property - def criticalPorePressure( self: Self ) -> npt.NDArray[ np.float64 ]: + def criticalPorePressure( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the critical pore pressure value.""" return self._criticalPorePressure @@ -652,7 +644,7 @@ def criticalPorePressure( self: Self, value: npt.NDArray[ np.float64 ] ) -> None self._criticalPorePressure = value @property - def criticalPorePressureIndex( self: Self ) -> npt.NDArray[ np.float64 ]: + def criticalPorePressureIndex( self: Self ) -> npt.NDArray[ np.float64 ] | None: """Get the critical pore pressure index value.""" return self._criticalPorePressureIndex @@ -660,7 +652,7 @@ def criticalPorePressureIndex( self: Self ) -> npt.NDArray[ np.float64 ]: def criticalPorePressureIndex( self: Self, value: npt.NDArray[ np.float64 ] ) -> None: self._criticalPorePressureIndex = value - def getAdvancedOutputValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ]: + def getAdvancedOutputValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ] | None: """Get the value of the advanced output wanted. Args: @@ -680,7 +672,11 @@ def getAdvancedOutputValue( self: Self, name: str ) -> npt.NDArray[ np.float64 ] else: raise NameError - _advancedOutput: AdvancedOutputValue = AdvancedOutputValue() + physicalConstants: PhysicalConstants + _elasticModuli: ElasticModuliValue + _mandatoryAttributes: MandatoryAttributesValue + _basicOutput: BasicOutputValue + _advancedOutput: AdvancedOutputValue def __init__( self: Self, @@ -701,6 +697,12 @@ def __init__( self.output.DeepCopy( mesh ) self.doComputeAdvancedOutputs: bool = computeAdvancedOutputs + self.physicalConstants = self.PhysicalConstants() + self._elasticModuli = self.ElasticModuliValue() + self._mandatoryAttributes = self.MandatoryAttributesValue() + self._basicOutput = self.BasicOutputValue() + self._advancedOutput = self.AdvancedOutputValue() + self._attributesToCreate: list[ AttributeEnum ] = [] # Logger. @@ -730,7 +732,7 @@ def applyFilter( self: Self ) -> bool: for attribute in self._attributesToCreate: attributeName: str = attribute.attributeName onPoints: bool = attribute.isOnPoints - array: npt.NDArray[ np.float64 ] + array: npt.NDArray[ np.float64 ] | None if attribute in ELASTIC_MODULI: array = self._elasticModuli.getElasticModulusValue( attributeName ) elif attribute in BASIC_OUTPUTS: @@ -811,8 +813,8 @@ def _checkMandatoryAttributes( self: Self ) -> bool: # Check the presence of the elastic moduli at the current time. self.computeYoungPoisson: bool - if self._elasticModuli.youngModulus.size == 0 and self._elasticModuli.poissonRatio.size == 0: - if self._elasticModuli.bulkModulus.size != 0 and self._elasticModuli.shearModulus.size != 0: + if self._elasticModuli.youngModulus is None and self._elasticModuli.poissonRatio is None: + if self._elasticModuli.bulkModulus is not None and self._elasticModuli.shearModulus is not None: self._elasticModuli.youngModulus = fcts.youngModulus( self._elasticModuli.bulkModulus, self._elasticModuli.shearModulus ) self._attributesToCreate.append( YOUNG_MODULUS ) @@ -825,8 +827,8 @@ def _checkMandatoryAttributes( self: Self ) -> bool: f"{ BULK_MODULUS.attributeName } or { SHEAR_MODULUS.attributeName } are missing to compute geomechanical outputs." ) return False - elif self._elasticModuli.bulkModulus.size == 0 and self._elasticModuli.shearModulus.size == 0: - if self._elasticModuli.youngModulus.size != 0 and self._elasticModuli.poissonRatio.size != 0: + elif self._elasticModuli.bulkModulus is None and self._elasticModuli.shearModulus is None: + if self._elasticModuli.youngModulus is not None and self._elasticModuli.poissonRatio is not None: self._elasticModuli.bulkModulus = fcts.bulkModulus( self._elasticModuli.youngModulus, self._elasticModuli.poissonRatio ) self._attributesToCreate.append( BULK_MODULUS ) @@ -846,8 +848,8 @@ def _checkMandatoryAttributes( self: Self ) -> bool: return False # Check the presence of the elastic moduli at the initial time. - if self._elasticModuli.bulkModulusT0.size == 0: - if self._elasticModuli.youngModulusT0.size != 0 and self._elasticModuli.poissonRatioT0.size != 0: + if self._elasticModuli.bulkModulusT0 is None: + if self._elasticModuli.youngModulusT0 is not None and self._elasticModuli.poissonRatioT0 is not None: self._elasticModuli.bulkModulusT0 = fcts.bulkModulus( self._elasticModuli.youngModulusT0, self._elasticModuli.poissonRatioT0 ) self._attributesToCreate.append( BULK_MODULUS_T0 ) @@ -1050,10 +1052,15 @@ def _computeRealEffectiveStressRatio( self: Self ) -> bool: Returns: bool: True if calculation successfully ended, False otherwise. """ - self._basicOutput.effectiveStressRatioReal = self._doComputeStressRatioReal( - self._mandatoryAttributes.effectiveStress, STRESS_EFFECTIVE_RATIO_REAL ) - - return True + if self._mandatoryAttributes.effectiveStress is not None: + self._basicOutput.effectiveStressRatioReal = self._doComputeStressRatioReal( + self._mandatoryAttributes.effectiveStress, STRESS_EFFECTIVE_RATIO_REAL ) + return True + else: + self.logger.error( + f"{ STRESS_EFFECTIVE_RATIO_REAL.attributeName } has not been computed, mandatory attribute { STRESS_EFFECTIVE.attributeName } is missing." + ) + return False def _doComputeTotalStress( self: Self, @@ -1094,17 +1101,26 @@ def _computeTotalStressInitial( self: Self ) -> bool: biotCoefficientT0: npt.NDArray[ np.float64 ] = fcts.biotCoefficient( self.physicalConstants.grainBulkModulus, self._elasticModuli.bulkModulusT0 ) - pressureT0: Union[ npt.NDArray[ np.float64 ], None ] = None + pressureT0: npt.NDArray[ np.float64 ] | None = None # Case pressureT0 is None, total stress = effective stress # (managed by doComputeTotalStress function) if self._mandatoryAttributes.pressure is not None: - # Get delta pressure to recompute pressure at initial time step (pressureTo) - pressureT0 = self._mandatoryAttributes.pressure - self._mandatoryAttributes.deltaPressure + if self._mandatoryAttributes.deltaPressure is not None: + pressureT0 = self._mandatoryAttributes.pressure - self._mandatoryAttributes.deltaPressure + else: + self.logger.error( f"Mandatory attribute { DELTA_PRESSURE.attributeName } is missing." ) + return False if not isAttributeInObject( self.output, STRESS_TOTAL_T0.attributeName, STRESS_TOTAL_T0.isOnPoints ): - self._basicOutput.totalStressT0 = self._doComputeTotalStress( self._mandatoryAttributes.effectiveStressT0, - pressureT0, biotCoefficientT0 ) - self._attributesToCreate.append( STRESS_TOTAL_T0 ) + if self._mandatoryAttributes.effectiveStressT0 is not None: + self._basicOutput.totalStressT0 = self._doComputeTotalStress( + self._mandatoryAttributes.effectiveStressT0, pressureT0, biotCoefficientT0 ) + self._attributesToCreate.append( STRESS_TOTAL_T0 ) + else: + self.logger.error( + f"{ STRESS_TOTAL_T0.attributeName } has not been computed, mandatory attribute { STRESS_EFFECTIVE_T0.attributeName } is missing." + ) + return False else: self._basicOutput.totalStressT0 = getArrayInObject( self.output, STRESS_TOTAL_T0.attributeName, STRESS_TOTAL_T0.isOnPoints ) @@ -1129,10 +1145,16 @@ def _computeTotalStresses( self: Self ) -> bool: # Compute total stress at current time step. if not isAttributeInObject( self.output, STRESS_TOTAL.attributeName, STRESS_TOTAL.isOnPoints ): - self._basicOutput.totalStress = self._doComputeTotalStress( self._mandatoryAttributes.effectiveStress, - self._mandatoryAttributes.pressure, - self._basicOutput.biotCoefficient ) - self._attributesToCreate.append( STRESS_TOTAL ) + if self._mandatoryAttributes.effectiveStress is not None and self._basicOutput.biotCoefficient is not None: + self._basicOutput.totalStress = self._doComputeTotalStress( self._mandatoryAttributes.effectiveStress, + self._mandatoryAttributes.pressure, + self._basicOutput.biotCoefficient ) + self._attributesToCreate.append( STRESS_TOTAL ) + else: + self.logger.error( + f"{ STRESS_TOTAL.attributeName } has not been computed, mandatory attributes { STRESS_EFFECTIVE.attributeName } or { BIOT_COEFFICIENT.attributeName } are missing." + ) + return False else: self._basicOutput.totalStress = getArrayInObject( self.output, STRESS_TOTAL.attributeName, STRESS_TOTAL.isOnPoints ) @@ -1140,8 +1162,14 @@ def _computeTotalStresses( self: Self ) -> bool: f"{ STRESS_TOTAL.attributeName } is already on the mesh, it has not been computed by the filter." ) # Compute total stress ratio. - self._basicOutput.totalStressRatioReal = self._doComputeStressRatioReal( self._basicOutput.totalStress, - STRESS_TOTAL_RATIO_REAL ) + if self._basicOutput.totalStress is not None: + self._basicOutput.totalStressRatioReal = self._doComputeStressRatioReal( self._basicOutput.totalStress, + STRESS_TOTAL_RATIO_REAL ) + else: + self.logger.error( + f"{ STRESS_TOTAL_RATIO_REAL.attributeName } has not been computed, Mandatory attribute { BIOT_COEFFICIENT.attributeName } is missing." + ) + return False return True @@ -1236,24 +1264,31 @@ def _computeElasticStrain( self: Self ) -> bool: Returns: bool: return True if calculation successfully ended, False otherwise. """ - deltaEffectiveStress = self._mandatoryAttributes.effectiveStress - self._mandatoryAttributes.effectiveStressT0 - - if not isAttributeInObject( self.output, STRAIN_ELASTIC.attributeName, STRAIN_ELASTIC.isOnPoints ): - if self.computeYoungPoisson: - self._basicOutput.elasticStrain = fcts.elasticStrainFromBulkShear( deltaEffectiveStress, - self._elasticModuli.bulkModulus, - self._elasticModuli.shearModulus ) + if self._mandatoryAttributes.effectiveStress is not None and self._mandatoryAttributes.effectiveStressT0 is not None: + deltaEffectiveStress = self._mandatoryAttributes.effectiveStress - self._mandatoryAttributes.effectiveStressT0 + + if not isAttributeInObject( self.output, STRAIN_ELASTIC.attributeName, STRAIN_ELASTIC.isOnPoints ): + if self.computeYoungPoisson: + self._basicOutput.elasticStrain = fcts.elasticStrainFromBulkShear( + deltaEffectiveStress, self._elasticModuli.bulkModulus, self._elasticModuli.shearModulus ) + else: + self._basicOutput.elasticStrain = fcts.elasticStrainFromYoungPoisson( + deltaEffectiveStress, self._elasticModuli.youngModulus, self._elasticModuli.poissonRatio ) + self._attributesToCreate.append( STRAIN_ELASTIC ) else: - self._basicOutput.elasticStrain = fcts.elasticStrainFromYoungPoisson( - deltaEffectiveStress, self._elasticModuli.youngModulus, self._elasticModuli.poissonRatio ) - self._attributesToCreate.append( STRAIN_ELASTIC ) - else: - self._basicOutput.totalStressT0 = getArrayInObject( self.output, STRAIN_ELASTIC.attributeName, - STRAIN_ELASTIC.isOnPoints ) - self.logger.warning( - f"{ STRAIN_ELASTIC.attributeName } is already on the mesh, it has not been computed by the filter." ) + self._basicOutput.totalStressT0 = getArrayInObject( self.output, STRAIN_ELASTIC.attributeName, + STRAIN_ELASTIC.isOnPoints ) + self.logger.warning( + f"{ STRAIN_ELASTIC.attributeName } is already on the mesh, it has not been computed by the filter." + ) - return True + return True + + else: + self.logger.error( + f"{ STRAIN_ELASTIC.attributeName } has not been computed, mandatory attributes { STRESS_EFFECTIVE.attributeName } or { STRESS_EFFECTIVE_T0.attributeName } are missing." + ) + return False def _computeReservoirStressPathReal( self: Self ) -> bool: """Compute reservoir stress paths. @@ -1263,8 +1298,14 @@ def _computeReservoirStressPathReal( self: Self ) -> bool: """ # create delta stress attribute for QC if not isAttributeInObject( self.output, STRESS_TOTAL_DELTA.attributeName, STRESS_TOTAL_DELTA.isOnPoints ): - self._basicOutput.deltaTotalStress = self._basicOutput.totalStress - self._basicOutput.totalStressT0 - self._attributesToCreate.append( STRESS_TOTAL_DELTA ) + if self._basicOutput.totalStress is not None and self._basicOutput.totalStressT0 is not None: + self._basicOutput.deltaTotalStress = self._basicOutput.totalStress - self._basicOutput.totalStressT0 + self._attributesToCreate.append( STRESS_TOTAL_DELTA ) + else: + self.logger.error( + f"{ STRESS_TOTAL_DELTA.attributeName } has not been computed, mandatory attributes { STRESS_TOTAL.attributeName } or { STRESS_TOTAL_T0.attributeName } are missing." + ) + return False else: self._basicOutput.deltaTotalStress = getArrayInObject( self.output, STRESS_TOTAL_DELTA.attributeName, STRESS_TOTAL_DELTA.isOnPoints ) @@ -1328,10 +1369,16 @@ def _computeCriticalTotalStressRatio( self: Self ) -> bool: """ if not isAttributeInObject( self.output, CRITICAL_TOTAL_STRESS_RATIO.attributeName, CRITICAL_TOTAL_STRESS_RATIO.isOnPoints ): - verticalStress: npt.NDArray[ np.float64 ] = self._basicOutput.totalStress[ :, 2 ] - self._advancedOutput.criticalTotalStressRatio = fcts.criticalTotalStressRatio( - self._mandatoryAttributes.pressure, verticalStress ) - self._attributesToCreate.append( CRITICAL_TOTAL_STRESS_RATIO ) + if self._basicOutput.totalStress is not None: + verticalStress: npt.NDArray[ np.float64 ] = self._basicOutput.totalStress[ :, 2 ] + self._advancedOutput.criticalTotalStressRatio = fcts.criticalTotalStressRatio( + self._mandatoryAttributes.pressure, verticalStress ) + self._attributesToCreate.append( CRITICAL_TOTAL_STRESS_RATIO ) + else: + self.logger.error( + f"{ CRITICAL_TOTAL_STRESS_RATIO.attributeName } has not been computed, mandatory attribute { STRESS_TOTAL.attributeName } is missing." + ) + return False else: self._advancedOutput.criticalTotalStressRatio = getArrayInObject( self.output, CRITICAL_TOTAL_STRESS_RATIO.attributeName, @@ -1342,12 +1389,18 @@ def _computeCriticalTotalStressRatio( self: Self ) -> bool: if not isAttributeInObject( self.output, TOTAL_STRESS_RATIO_THRESHOLD.attributeName, TOTAL_STRESS_RATIO_THRESHOLD.isOnPoints ): - mask: npt.NDArray[ np.bool_ ] = np.argmin( np.abs( self._basicOutput.totalStress[ :, :2 ] ), axis=1 ) - horizontalStress: npt.NDArray[ np.float64 ] = self._basicOutput.totalStress[ :, :2 ][ - np.arange( self._basicOutput.totalStress[ :, :2 ].shape[ 0 ] ), mask ] - self._advancedOutput.stressRatioThreshold = fcts.totalStressRatioThreshold( - self._mandatoryAttributes.pressure, horizontalStress ) - self._attributesToCreate.append( TOTAL_STRESS_RATIO_THRESHOLD ) + if self._basicOutput.totalStress is not None: + mask: npt.NDArray[ np.bool_ ] = np.argmin( np.abs( self._basicOutput.totalStress[ :, :2 ] ), axis=1 ) + horizontalStress: npt.NDArray[ np.float64 ] = self._basicOutput.totalStress[ :, :2 ][ + np.arange( self._basicOutput.totalStress[ :, :2 ].shape[ 0 ] ), mask ] + self._advancedOutput.stressRatioThreshold = fcts.totalStressRatioThreshold( + self._mandatoryAttributes.pressure, horizontalStress ) + self._attributesToCreate.append( TOTAL_STRESS_RATIO_THRESHOLD ) + else: + self.logger.error( + f"{ TOTAL_STRESS_RATIO_THRESHOLD.attributeName } has not been computed, mandatory attribute { STRESS_TOTAL.attributeName } is missing." + ) + return False else: self._advancedOutput.stressRatioThreshold = getArrayInObject( self.output, TOTAL_STRESS_RATIO_THRESHOLD.attributeName, @@ -1366,10 +1419,16 @@ def _computeCriticalPorePressure( self: Self ) -> bool: """ if not isAttributeInObject( self.output, CRITICAL_PORE_PRESSURE.attributeName, CRITICAL_PORE_PRESSURE.isOnPoints ): - self._advancedOutput.criticalPorePressure = fcts.criticalPorePressure( - -1.0 * self._basicOutput.totalStress, self.physicalConstants.rockCohesion, - self.physicalConstants.frictionAngle ) - self._attributesToCreate.append( CRITICAL_PORE_PRESSURE ) + if self._basicOutput.totalStress is not None: + self._advancedOutput.criticalPorePressure = fcts.criticalPorePressure( + -1.0 * self._basicOutput.totalStress, self.physicalConstants.rockCohesion, + self.physicalConstants.frictionAngle ) + self._attributesToCreate.append( CRITICAL_PORE_PRESSURE ) + else: + self.logger.error( + f"{ CRITICAL_PORE_PRESSURE.attributeName } has not been computed, mandatory attribute { STRESS_TOTAL.attributeName } is missing." + ) + return False else: self._advancedOutput.criticalPorePressure = getArrayInObject( self.output, CRITICAL_PORE_PRESSURE.attributeName, diff --git a/geos-utils/src/geos/utils/GeosOutputsConstants.py b/geos-utils/src/geos/utils/GeosOutputsConstants.py index 3e684aaac..8a53dca1d 100644 --- a/geos-utils/src/geos/utils/GeosOutputsConstants.py +++ b/geos-utils/src/geos/utils/GeosOutputsConstants.py @@ -6,7 +6,7 @@ from typing_extensions import Self __doc__ = """ -GeosOutputsConstants module defines usefull constant names such as attribute +GeosOutputsConstants module defines useful constant names such as attribute names, domain names, phase types, and the lists of attribute names to process. .. WARNING:: @@ -28,7 +28,7 @@ class AttributeEnum( Enum ): def __init__( self: Self, attributeName: str, nbComponent: int, onPoints: bool ) -> None: - """Define the enumeration to store attrbute properties. + """Define the enumeration to store attribute properties. Args: attributeName (str): name of the attribute @@ -134,7 +134,7 @@ class GeosMeshOutputsEnum( AttributeEnum ): DELTA_PRESSURE = ( "deltaPressure", 1, False ) MASS = ( "mass", 1, False ) - # geomechanic attributes + # geomechanics attributes ROCK_DENSITY = ( "density", 1, False ) PERMEABILITY = ( "permeability", 1, False ) POROSITY = ( "porosity", 1, False ) @@ -182,11 +182,11 @@ class PostProcessingOutputsEnum( AttributeEnum ): SPECIFIC_GRAVITY = ( "specificGravity", 1, False ) LITHOSTATIC_STRESS = ( "stressLithostatic", 1, False ) STRESS_EFFECTIVE_INITIAL = ( "stressEffectiveInitial", 6, False ) - STRESS_EFFECTIVE_RATIO_REAL = ( "stressEffectiveRatio_real", 6, False ) - STRESS_EFFECTIVE_RATIO_OED = ( "stressEffectiveRatio_oed", 6, False ) + STRESS_EFFECTIVE_RATIO_REAL = ( "stressEffectiveRatio_real", 1, False ) + STRESS_EFFECTIVE_RATIO_OED = ( "stressEffectiveRatio_oed", 1, False ) STRESS_TOTAL = ( "stressTotal", 6, False ) STRESS_TOTAL_INITIAL = ( "stressTotalInitial", 6, False ) - STRESS_TOTAL_RATIO_REAL = ( "stressTotalRatio_real", 6, False ) + STRESS_TOTAL_RATIO_REAL = ( "stressTotalRatio_real", 1, False ) STRESS_TOTAL_DELTA = ( "deltaStressTotal", 6, False ) STRAIN_ELASTIC = ( "strainElastic", 6, False ) RSP_OED = ( "rsp_oed", 1, False )