From bb2911efe62dc31f9ef4771252731b96b9cf5f73 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Tue, 9 Sep 2025 14:36:04 +0200
Subject: [PATCH 01/62] Move SurfaceGeomechanicsFilter and refactor functions
---
.../geos_posp/filters/SurfaceGeomechanics.py | 457 -----------------
geos-processing/pyproject.toml | 70 +++
.../post_processing/SurfaceGeomechanics.py | 473 ++++++++++++++++++
.../src/geos/utils/GeosOutputsConstants.py | 11 +-
4 files changed, 553 insertions(+), 458 deletions(-)
delete mode 100644 geos-posp/src/geos_posp/filters/SurfaceGeomechanics.py
create mode 100644 geos-processing/pyproject.toml
create mode 100644 geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
diff --git a/geos-posp/src/geos_posp/filters/SurfaceGeomechanics.py b/geos-posp/src/geos_posp/filters/SurfaceGeomechanics.py
deleted file mode 100644
index 7e50cbf72..000000000
--- a/geos-posp/src/geos_posp/filters/SurfaceGeomechanics.py
+++ /dev/null
@@ -1,457 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0
-# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Martin Lemay
-# ruff: noqa: E402 # disable Module level import not at top of file
-import geos.geomechanics.processing.geomechanicsCalculatorFunctions as fcts
-import geos.utils.geometryFunctions as geom
-import numpy as np
-import numpy.typing as npt
-import vtkmodules.util.numpy_support as vnp
-from geos.utils.algebraFunctions import (
- getAttributeMatrixFromVector,
- getAttributeVectorFromMatrix,
-)
-from geos.utils.GeosOutputsConstants import (
- ComponentNameEnum,
- GeosMeshOutputsEnum,
- PostProcessingOutputsEnum,
- getAttributeToConvertFromLocalToXYZ,
-)
-from geos.utils.Logger import Logger, getLogger
-from geos.utils.PhysicalConstants import (
- DEFAULT_FRICTION_ANGLE_RAD,
- DEFAULT_ROCK_COHESION,
-)
-from typing_extensions import Self
-from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase
-from vtkmodules.vtkCommonCore import (
- vtkDataArray,
- vtkDoubleArray,
- vtkInformation,
- vtkInformationVector,
-)
-from vtkmodules.vtkCommonDataModel import (
- vtkPolyData, )
-
-from geos.mesh.utils.arrayModifiers import createAttribute
-from geos.mesh.utils.arrayHelpers import (
- getArrayInObject,
- getAttributeSet,
- isAttributeInObject,
-)
-
-__doc__ = """
-SurfaceGeomechanics module is a vtk filter that allows to compute Geomechanical
-properties on surfaces.
-
-Filter input and output types are vtkPolyData.
-
-To use the filter:
-
-.. code-block:: python
-
- from filters.SurfaceGeomechanics import SurfaceGeomechanics
-
- # filter inputs
- logger :Logger
- input :vtkPolyData
- rockCohesion :float
- frictionAngle :float # angle in radian
-
- # instanciate the filter
- surfaceGeomechanicsFilter :SurfaceGeomechanics = SurfaceGeomechanics()
- # set the logger
- surfaceGeomechanicsFilter.SetLogger(logger)
- # set input data object
- surfaceGeomechanicsFilter.SetInputDataObject(input)
- # set rock cohesion anf friction angle
- surfaceGeomechanicsFilter.SetRockCohesion(rockCohesion)
- surfaceGeomechanicsFilter.SetFrictionAngle(frictionAngle)
- # do calculations
- surfaceGeomechanicsFilter.Update()
- # get output object
- output :vtkPolyData = surfaceGeomechanicsFilter.GetOutputDataObject(0)
- # get created attribute names
- newAttributeNames :set[str] = surfaceGeomechanicsFilter.GetNewAttributeNames()
-"""
-
-
-class SurfaceGeomechanics( VTKPythonAlgorithmBase ):
-
- def __init__( self: Self ) -> None:
- """Vtk filter to compute geomechanical surfacic attributes.
-
- Input and Output objects are a vtkMultiBlockDataSet containing surface
- objects with Normals and Tangential attributes.
- """
- super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkPolyData" ) # type: ignore[no-untyped-call]
-
- # output surface mesh
- self.m_output: vtkPolyData
- # attributes are either on points or on cells
- self.m_attributeOnPoints: bool = False
- # rock cohesion (Pa)
- self.m_rockCohesion: float = DEFAULT_ROCK_COHESION
- # friction angle (rad)
- self.m_frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD
- # new created attributes names
- self.m_newAttributeNames: set[ str ] = set()
-
- # logger
- self.m_logger: Logger = getLogger( "Surface Geomechanics Filter" )
-
- def SetRockCohesion( self: Self, rockCohesion: float ) -> None:
- """Set rock cohesion value.
-
- Args:
- rockCohesion (float): rock cohesion (Pa)
- """
- self.m_rockCohesion = rockCohesion
-
- def SetFrictionAngle( self: Self, frictionAngle: float ) -> None:
- """Set friction angle value.
-
- Args:
- frictionAngle (float): friction angle (rad)
- """
- self.m_frictionAngle = frictionAngle
-
- def SetLogger( self: Self, logger: Logger ) -> None:
- """Set the logger.
-
- Args:
- logger (Logger): logger
- """
- self.m_logger = logger
-
- def GetNewAttributeNames( self: Self ) -> set[ str ]:
- """Get the set of new attribute names that were created.
-
- Returns:
- set[str]: set of new attribute names
- """
- return self.m_newAttributeNames
-
- 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(), "vtkPolyData" )
- return 1
-
- 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 RequestData(
- self: Self,
- request: vtkInformation, # noqa: F841
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestData.
-
- Args:
- request (vtkInformation): request
- inInfoVec (list[vtkInformationVector]): input objects
- outInfoVec (vtkInformationVector): output objects
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- try:
- input = vtkPolyData.GetData( inInfoVec[ 0 ] )
- self.m_output = self.GetOutputData( outInfoVec, 0 ) # type: ignore[no-untyped-call]
-
- assert input is not None, "Input object is null."
- assert self.m_output is not None, "Output pipeline is null."
-
- self.m_output.ShallowCopy( input )
- # conversion of vectorial attributes from Normal/Tangent basis to xyz basis
- assert ( self.convertLocalToXYZBasisAttributes() ), "Error while converting Local to XYZ basis attributes"
- # compute shear capacity utilization
- assert self.computeShearCapacityUtilization(), "Error while computing SCU."
- self.Modified()
- except AssertionError as e:
- mess: str = "Surface geomechanics attributes calculation failed due to:"
- self.m_logger.error( mess )
- self.m_logger.error( e, exc_info=True )
- return 0
- except Exception as e:
- mess1: str = "Surface geomechanics attributes calculation failed due to:"
- self.m_logger.critical( mess1 )
- self.m_logger.critical( e, exc_info=True )
- return 0
- mess2: str = "Surface geomechanics attributes were successfully computed."
- self.m_logger.info( mess2 )
- return 1
-
- def convertLocalToXYZBasisAttributes( self: Self ) -> bool:
- """Convert vectorial property coordinates from Local to canonic basis.
-
- Returns:
- bool: True if calculation successfully ended, False otherwise
- """
- # look for the list of attributes to convert
- attributesToConvert: set[ str ] = self.getAttributesToConvertFromLocalToXYZ()
- if len( attributesToConvert ) == 0:
- self.m_logger.info( "No attribute to convert from local to XYZ basis were found." )
- return False
-
- # get local coordinate vectors
- normalTangentVectors: npt.NDArray[ np.float64 ] = self.getNormalTangentsVectors()
- # do conversion for each attribute
- for attributName in attributesToConvert:
- # skip attribute if it is already in the object
- newAttrName: str = attributName + "_" + ComponentNameEnum.XYZ.name
- if isAttributeInObject( self.m_output, newAttrName, False ):
- continue
-
- attr: vtkDoubleArray = self.m_output.GetCellData().GetArray( attributName )
- assert attr is not None, "Attribute {attributName} is undefined."
- assert attr.GetNumberOfComponents() > 2, ( "Dimension of the attribute " +
- " must be equal or grater than 3." )
-
- attrArray: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy( attr ) # type: ignore[no-untyped-call]
- newAttrArray: npt.NDArray[ np.float64 ] = self.computeNewCoordinates( attrArray, normalTangentVectors,
- True )
-
- # create attribute
- createAttribute(
- self.m_output,
- newAttrArray,
- newAttrName,
- ComponentNameEnum.XYZ.value,
- False,
- )
- self.m_newAttributeNames.add( newAttrName )
- return True
-
- def convertXYZToLocalBasisAttributes( self: Self ) -> bool:
- """Convert vectorial property coordinates from canonic to local basis.
-
- Returns:
- bool: True if calculation successfully ended, False otherwise
- """
- # look for the list of attributes to convert
- # empty but to update if needed in the future
- attributesToConvert: set[ str ] = set()
- if len( attributesToConvert ) == 0:
- self.m_logger.info( "No attribute to convert from local to " + "canonic basis were found." )
- return False
-
- # get local coordinate vectors
- normalTangentVectors: npt.NDArray[ np.float64 ] = self.getNormalTangentsVectors()
- for attributName in attributesToConvert:
- # skip attribute if it is already in the object
- newAttrName: str = ( attributName + "_" + ComponentNameEnum.NORMAL_TANGENTS.name )
- if isAttributeInObject( self.m_output, newAttrName, False ):
- continue
- attr: vtkDoubleArray = self.m_output.GetCellData().GetArray( attributName )
- assert attr is not None, "Attribute {attributName} is undefined."
- assert attr.GetNumberOfComponents() > 2, ( "Dimension of the attribute " +
- " must be equal or grater than 3." )
-
- attrArray: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy( attr ) # type: ignore[no-untyped-call]
- newAttrArray: npt.NDArray[ np.float64 ] = self.computeNewCoordinates( attrArray, normalTangentVectors,
- False )
-
- # create attribute
- createAttribute(
- self.m_output,
- newAttrArray,
- newAttrName,
- ComponentNameEnum.NORMAL_TANGENTS.value,
- False,
- )
- self.m_newAttributeNames.add( newAttrName )
- return True
-
- def getAttributesToConvertFromLocalToXYZ( self: Self ) -> set[ str ]:
- """Get the list of attributes to convert from local to XYZ basis.
-
- Returns:
- set[str]: Set of the attribute names.
- """
- return self.filterAttributesToConvert( getAttributeToConvertFromLocalToXYZ() )
-
- def filterAttributesToConvert( self: Self, attributesToFilter0: set[ str ] ) -> set[ str ]:
- """Filter the set of attribute names if they are vectorial and present.
-
- Args:
- attributesToFilter0 (set[str]): set of attribute names to filter.
-
- Returns:
- set[str]: Set of the attribute names.
- """
- attributesFiltered: set[ str ] = set()
- attributeSet: set[ str ] = getAttributeSet( self.m_output, False )
- for attributName in attributesToFilter0:
- if attributName in attributeSet:
- attr: vtkDataArray = self.m_output.GetCellData().GetArray( attributName )
- if attr.GetNumberOfComponents() > 2:
- attributesFiltered.add( attributName )
- return attributesFiltered
-
- def computeNewCoordinates(
- self: Self,
- attrArray: npt.NDArray[ np.float64 ],
- normalTangentVectors: npt.NDArray[ np.float64 ],
- fromLocalToYXZ: bool,
- ) -> npt.NDArray[ np.float64 ]:
- """Compute the coordinates of a vectorial attribute.
-
- Args:
- attrArray (npt.NDArray[np.float64]): vector of attribute values
- normalTangentVectors (npt.NDArray[np.float64]): 3xNx3 local vector
- coordinates
- fromLocalToYXZ (bool): if True, conversion is done from local to XYZ
- basis, otherwise conversion is done from XZY to Local basis.
-
- Returns:
- npt.NDArray[np.float64]: Vector of new coordinates of the attribute.
- """
- attrArrayNew = np.full_like( attrArray, np.nan )
- # for each cell
- for i in range( attrArray.shape[ 0 ] ):
- # get the change of basis matrix
- localBasis: npt.NDArray[ np.float64 ] = normalTangentVectors[ :, i, : ]
- changeOfBasisMatrix = self.computeChangeOfBasisMatrix( localBasis, fromLocalToYXZ )
- if attrArray.shape[ 1 ] == 3:
- attrArrayNew[ i ] = self.computeNewCoordinatesVector3( attrArray[ i ], changeOfBasisMatrix )
- else:
- attrArrayNew[ i ] = self.computeNewCoordinatesVector6( attrArray[ i ], changeOfBasisMatrix )
-
- assert np.any( np.isfinite( attrArrayNew ) ), "Attribute new coordinate calculation failed."
- return attrArrayNew
-
- def computeNewCoordinatesVector3(
- self: Self,
- vector: npt.NDArray[ np.float64 ],
- changeOfBasisMatrix: npt.NDArray[ np.float64 ],
- ) -> npt.NDArray[ np.float64 ]:
- """Compute attribute new coordinates of vector of size 3.
-
- Args:
- vector (npt.NDArray[np.float64]): input coordinates.
- changeOfBasisMatrix (npt.NDArray[np.float64]): change of basis matrix
-
- Returns:
- npt.NDArray[np.float64]: new coordinates
- """
- return geom.computeCoordinatesInNewBasis( vector, changeOfBasisMatrix )
-
- def computeNewCoordinatesVector6(
- self: Self,
- vector: npt.NDArray[ np.float64 ],
- changeOfBasisMatrix: npt.NDArray[ np.float64 ],
- ) -> npt.NDArray[ np.float64 ]:
- """Compute attribute new coordinates of vector of size > 3.
-
- Args:
- vector (npt.NDArray[np.float64]): input coordinates.
- changeOfBasisMatrix (npt.NDArray[np.float64]): change of basis matrix
-
- Returns:
- npt.NDArray[np.float64]: new coordinates
- """
- attributeMatrix: npt.NDArray[ np.float64 ] = getAttributeMatrixFromVector( vector )
- attributeMatrixNew: npt.NDArray[ np.float64 ] = np.full_like( attributeMatrix, np.nan )
- # for each column of the matrix
- for j in range( attributeMatrix.shape[ 1 ] ):
- attributeMatrixNew[ :, j ] = geom.computeCoordinatesInNewBasis( attributeMatrix[ :, j ],
- changeOfBasisMatrix )
- return getAttributeVectorFromMatrix( attributeMatrixNew, vector.size )
-
- def computeChangeOfBasisMatrix( self: Self, localBasis: npt.NDArray[ np.float64 ],
- fromLocalToYXZ: bool ) -> npt.NDArray[ np.float64 ]:
- """Compute the change of basis matrix according to local coordinates.
-
- Args:
- localBasis (npt.NDArray[np.float64]): local coordinate vectors.
- fromLocalToYXZ (bool): if True, change of basis matrix is from local
- to XYZ bases, otherwise it is from XYZ to local bases.
-
- Returns:
- npt.NDArray[np.float64]: change of basis matrix.
- """
- P: npt.NDArray[ np.float64 ] = np.transpose( localBasis )
- if fromLocalToYXZ:
- return P
- # inverse the change of basis matrix
- return np.linalg.inv( P ).astype( np.float64 )
-
- def getNormalTangentsVectors( self: Self ) -> npt.NDArray[ np.float64 ]:
- """Compute the change of basis matrix from Local to XYZ bases.
-
- Returns:
- npt.NDArray[np.float64]: Nx3 matrix of local vector coordinates.
- """
- # get normal and first tangent components
- normals: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy(
- self.m_output.GetCellData().GetNormals() ) # type: ignore[no-untyped-call]
- assert normals is not None, "Normal attribute was not found."
- tangents1: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy(
- self.m_output.GetCellData().GetTangents() ) # type: ignore[no-untyped-call]
- assert tangents1 is not None, "Tangents attribute was not found."
-
- # compute second tangential component
- tangents2: npt.NDArray[ np.float64 ] = np.cross( normals, tangents1, axis=1 ).astype( np.float64 )
- assert tangents2 is not None, "Local basis third axis was not computed."
-
- # put vectors as columns
- return np.array( ( normals, tangents1, tangents2 ) )
-
- def computeShearCapacityUtilization( self: Self ) -> bool:
- """Compute the shear capacity utilization on surface.
-
- Returns:
- bool: True if calculation successfully ended, False otherwise.
- """
- try:
- SCUAttributeName: str = PostProcessingOutputsEnum.SCU.attributeName
- if not isAttributeInObject( self.m_output, SCUAttributeName, self.m_attributeOnPoints ):
- tractionAttributeName: str = GeosMeshOutputsEnum.TRACTION.attributeName
- traction: npt.NDArray[ np.float64 ] = getArrayInObject( self.m_output, tractionAttributeName,
- self.m_attributeOnPoints )
- assert traction is not None, ( f"{tractionAttributeName}" + " attribute is undefined." )
-
- scuAttribute: npt.NDArray[ np.float64 ] = fcts.shearCapacityUtilization(
- traction, self.m_rockCohesion, self.m_frictionAngle )
- createAttribute(
- self.m_output,
- scuAttribute,
- SCUAttributeName,
- (),
- self.m_attributeOnPoints,
- )
- self.m_newAttributeNames.add( SCUAttributeName )
-
- except AssertionError as e:
- self.m_logger.error( "Shear Capacity Utilization was not computed due to:" )
- self.m_logger.error( str( e ) )
- return False
- return True
diff --git a/geos-processing/pyproject.toml b/geos-processing/pyproject.toml
new file mode 100644
index 000000000..3d947f672
--- /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 = []
diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
new file mode 100644
index 000000000..f5e83be6f
--- /dev/null
+++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
@@ -0,0 +1,473 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: Martin Lemay
+# ruff: noqa: E402 # disable Module level import not at top of file
+from enum import Enum
+import geos.geomechanics.processing.geomechanicsCalculatorFunctions as fcts
+import geos.utils.geometryFunctions as geom
+import numpy as np
+import numpy.typing as npt
+import vtkmodules.util.numpy_support as vnp
+from geos.utils.algebraFunctions import (
+ getAttributeMatrixFromVector,
+ getAttributeVectorFromMatrix,
+)
+from geos.utils.GeosOutputsConstants import (
+ ComponentNameEnum,
+ GeosMeshOutputsEnum,
+ PostProcessingOutputsEnum,
+ getAttributeToConvertFromLocalToXYZ,
+ getAttributeToConvertFromXYZToLocal,
+)
+from geos.utils.Logger import logging, Logger, getLogger
+from geos.utils.PhysicalConstants import (
+ DEFAULT_FRICTION_ANGLE_RAD,
+ DEFAULT_ROCK_COHESION,
+)
+from typing_extensions import Self
+from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase
+from vtkmodules.vtkCommonCore import (
+ vtkDataArray,
+ vtkDoubleArray,
+)
+from vtkmodules.vtkCommonDataModel import (
+ vtkPolyData, )
+
+from geos.mesh.utils.arrayModifiers import createAttribute
+from geos.mesh.utils.arrayHelpers import (
+ getArrayInObject,
+ getAttributeSet,
+ isAttributeInObject,
+)
+
+
+__doc__ = """
+SurfaceGeomechanics vtk filter allows to compute Geomechanical
+properties on surfaces.
+
+Filter input and output types are vtkPolyData.
+
+To use the filter:
+
+.. code-block:: python
+
+ from geos.processing.post_processing.SurfaceGeomechanics import SurfaceGeomechanics
+
+ # filter inputs
+ inputMesh: vtkPolyData
+
+ # Optional inputs
+ rockCohesion: float # Pa
+ frictionAngle: float # angle in radian
+ speHandler: bool # defaults to false
+
+ # Instantiate the filter
+ filter: SurfaceGeomechanics = SurfaceGeomechanics( inputMesh, speHandler )
+
+ # Set the handler of yours (only if speHandler is True).
+ yourHandler: logging.Handler
+ filter.SetLoggerHandler( yourHandler )
+
+ # Set rock cohesion and friction angle (optional)
+ filter.SetRockCohesion( rockCohesion )
+ filter.SetFrictionAngle( frictionAngle )
+
+ # Do calculations
+ filter.applyFilter()
+
+ # Get output object
+ output: vtkPolyData = filter.GetOutputMesh()
+
+ # Get created attribute names
+ newAttributeNames: set[str] = filter.GetNewAttributeNames()
+"""
+loggerTitle: str = "Surface Geomechanics"
+
+class SurfaceGeomechanics( VTKPythonAlgorithmBase ):
+
+ def __init__( self: Self,
+ surfacicMesh: vtkPolyData,
+ speHandler: bool = False ) -> None:
+ """Vtk filter to compute geomechanical surfacic attributes.
+
+ Input and Output objects are a vtkPolydata with surfaces
+ objects with Normals and Tangential attributes.
+
+ Args:
+ surfacicMesh (vtkPolyData): The input surfacic mesh.
+ 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="vtkPolyData" ) # type: ignore[no-untyped-call]
+
+ # Logger
+ self.logger: Logger
+ if not speHandler:
+ self.logger = getLogger( loggerTitle, True )
+ else:
+ self.logger = logging.getLogger( loggerTitle )
+ self.logger.setLevel( logging.INFO )
+
+ # Input surfacic mesh
+ if not surfacicMesh.IsA( "vtkPolyData" ):
+ self.logger.error( f" surfacicMesh parameter is expected to be a vtkPolyData, not a {type(surfacicMesh)}." )
+ self.inputMesh: vtkPolyData = surfacicMesh
+ # Output surfacic mesh
+ self.outputMesh: vtkPolyData
+
+ # Attributes are either on points or on cells
+ self.attributeOnPoints: bool = False
+ # Rock cohesion (Pa)
+ self.rockCohesion: float = DEFAULT_ROCK_COHESION
+ # Friction angle (rad)
+ self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD
+ # New created attributes names
+ self.newAttributeNames: set[ str ] = set()
+
+
+ def SetLoggerHandler( self: Self, handler: Logger ) -> None:
+ """Set a specific handler for the filter 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:
+ handler (logging.Handler): The handler to add.
+ """
+ if not self.logger.hasHandlers():
+ self.logger.addHandler( handler )
+ else:
+ self.logger.warning(
+ "The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization."
+ )
+
+ def SetRockCohesion( self: Self, rockCohesion: float ) -> None:
+ """Set rock cohesion value. Defaults to 0.0 Pa.
+
+ Args:
+ rockCohesion (float): The rock cohesion in Pascal.
+ """
+ self.rockCohesion = rockCohesion
+
+ def SetFrictionAngle( self: Self, frictionAngle: float ) -> None:
+ """Set friction angle value. Defaults to 10 / 180 * pi rad.
+
+ Args:
+ frictionAngle (float): The friction angle in radians.
+ """
+ self.frictionAngle = frictionAngle
+
+ def GetNewAttributeNames( self: Self ) -> set[ str ]:
+ """Get the set of new attribute names that were created.
+
+ Returns:
+ set[str]: The set of new attribute names.
+ """
+ return self.newAttributeNames
+
+ def applyFilter( self: Self ) -> bool:
+ """Compute Geomechanical properties on input surface.
+
+ Returns:
+ int: 1 if calculation successfully ended, 0 otherwise.
+ """
+ self.logger.info( f"Applying filter { self.logger.name }." )
+
+ self.outputMesh = vtkPolyData()
+ self.outputMesh.ShallowCopy( self.inputMesh )
+
+ # Conversion of vectorial attributes from Normal/Tangent basis to xyz basis
+ if not self.convertLocalToXYZBasisAttributes():
+ self.logger.error( "Error while converting Local to XYZ basis attributes." )
+ # Compute shear capacity utilization
+ if not self.computeShearCapacityUtilization():
+ self.logger.error( "Error while computing SCU." )
+
+ return True
+
+ def convertLocalToXYZBasisAttributes( self: Self ) -> bool:
+ """Convert vectorial property coordinates from local to canonic basis.
+
+ Returns:
+ bool: True if calculation successfully ended, False otherwise
+ """
+ # Look for the list of attributes to convert
+ basisFrom = "local"
+ basisTo = "XYZ"
+ attributesToConvert: set[ str ] = self.__getAttributesToConvert( basisFrom, basisTo )
+ if len( attributesToConvert ) == 0:
+ self.logger.error( f"No attribute to convert from {basisFrom} to {basisTo} basis were found." )
+ return False
+
+ # Get local coordinate vectors
+ normalTangentVectors: npt.NDArray[ np.float64 ] = self.getNormalTangentsVectors()
+ # Do conversion from local to canonic for each attribute
+ for attrName in attributesToConvert:
+ # Skip attribute if it is already in the object
+ newAttrName: str = attrName + "_" + ComponentNameEnum.XYZ.name
+ if isAttributeInObject( self.outputMesh, newAttrName, False ):
+ continue
+
+ attr: vtkDoubleArray = self.outputMesh.GetCellData().GetArray( attrName )
+ if attr is None:
+ self.logger.error( f"Attribute {attrName} is undefined." )
+ if not attr.GetNumberOfComponents() > 2:
+ self.logger.error( f"Dimension of the attribute {attrName} must be equal or greater than 3." )
+
+ attrArray: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy( attr ) # type: ignore[no-untyped-call]
+ newAttrArray: npt.NDArray[ np.float64 ] = self.computeNewCoordinates( attrArray, normalTangentVectors,
+ True )
+
+ # create attribute
+ if not createAttribute(
+ self.outputMesh,
+ newAttrArray,
+ newAttrName,
+ ComponentNameEnum.XYZ.value,
+ False,
+ logger = self.logger
+ ):
+ self.logger.error( f"Failed to create attribute {newAttrName}." )
+ else:
+ self.logger.info( f"Attribute {newAttrName} added to the output mesh." )
+ self.newAttributeNames.add( newAttrName )
+ return True
+
+ def convertXYZToLocalBasisAttributes( self: Self ) -> bool:
+ """Convert vectorial property coordinates from canonic to local basis.
+
+ Returns:
+ bool: True if calculation successfully ended, False otherwise
+ """
+ # Look for the list of attributes to convert
+ basisFrom = "canonical"
+ basisTo = "local"
+ attributesToConvert: set[ str ] = self.__getAttributesToConvert( basisFrom, basisTo )
+ if len( attributesToConvert ) == 0:
+ self.logger.error( "No attribute to convert from canonic to local basis were found." )
+ return False
+
+ # get local coordinate vectors
+ normalTangentVectors: npt.NDArray[ np.float64 ] = self.getNormalTangentsVectors()
+ # Do conversion from canonic to local for each attribute
+ for attrName in attributesToConvert:
+ # Skip attribute if it is already in the object
+ newAttrName: str = attrName + "_" + ComponentNameEnum.NORMAL_TANGENTS.name
+ if isAttributeInObject( self.outputMesh, newAttrName, False ):
+ continue
+
+ attr: vtkDoubleArray = self.outputMesh.GetCellData().GetArray( attrName )
+ if attr is None:
+ self.logger.error( f"Attribute {attrName} is undefined." )
+ if not attr.GetNumberOfComponents() > 2:
+ self.logger.error( f"Dimension of the attribute {attrName} must be equal or greater than 3." )
+
+ attrArray: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy( attr ) # type: ignore[no-untyped-call]
+ newAttrArray: npt.NDArray[ np.float64 ] = self.computeNewCoordinates( attrArray, normalTangentVectors,
+ False )
+
+ # Create attribute
+ if not createAttribute(
+ self.outputMesh,
+ newAttrArray,
+ newAttrName,
+ ComponentNameEnum.NORMAL_TANGENTS.value,
+ False,
+ logger = self.logger
+ ):
+ self.logger.error( f"Failed to create attribute {newAttrName}." )
+ return False
+ else:
+ self.logger.info( f"Attribute {newAttrName} added to the output mesh." )
+ self.newAttributeNames.add( newAttrName )
+ return True
+
+
+ def __getAttributesToConvert( self: Self, basisFrom: str, basisTo: str ):
+ """Get the list of attributes to convert from one basis to another.
+ Choices of basis are "local" or "XYZ".
+
+ Returns:
+ set[ str ]: Set of the attributes names
+ """
+ if basisFrom == "local" and basisTo == "XYZ":
+ return getAttributeToConvertFromLocalToXYZ()
+ elif basisFrom == "XYZ" and basisTo == "local":
+ return getAttributeToConvertFromXYZToLocal()
+ else:
+ self.logger.error( f"Unknow basis named {basisFrom} or {basisTo}." )
+
+
+ def filterAttributesToConvert( self: Self, attributesToFilter: set[ str ] ) -> set[ str ]:
+ """Filter the set of attribute names if they are vectorial and present.
+
+ Args:
+ attributesToFilter (set[str]): set of attribute names to filter.
+
+ Returns:
+ set[str]: Set of the attribute names.
+ """
+ attributesFiltered: set[ str ] = set()
+ attributeSet: set[ str ] = getAttributeSet( self.outputMesh, False )
+ for attrName in attributesToFilter:
+ if attrName in attributeSet:
+ attr: vtkDataArray = self.outputMesh.GetCellData().GetArray( attrName )
+ if attr.GetNumberOfComponents() > 2:
+ attributesFiltered.add( attrName )
+ return attributesFiltered
+
+ def computeNewCoordinates(
+ self: Self,
+ attrArray: npt.NDArray[ np.float64 ],
+ normalTangentVectors: npt.NDArray[ np.float64 ],
+ fromLocalToYXZ: bool,
+ ) -> npt.NDArray[ np.float64 ]:
+ """Compute the coordinates of a vectorial attribute.
+
+ Args:
+ attrArray (npt.NDArray[np.float64]): vector of attribute values
+ normalTangentVectors (npt.NDArray[np.float64]): 3xNx3 local vector
+ coordinates
+ fromLocalToYXZ (bool): if True, conversion is done from local to XYZ
+ basis, otherwise conversion is done from XZY to Local basis.
+
+ Returns:
+ npt.NDArray[np.float64]: Vector of new coordinates of the attribute.
+ """
+ attrArrayNew = np.full_like( attrArray, np.nan )
+ # For each cell
+ for i in range( attrArray.shape[ 0 ] ):
+ # Get the change of basis matrix
+ localBasis: npt.NDArray[ np.float64 ] = normalTangentVectors[ :, i, : ]
+ changeOfBasisMatrix = self.__computeChangeOfBasisMatrix( localBasis, fromLocalToYXZ )
+ if attrArray.shape[ 1 ] == 3:
+ attrArrayNew[ i ] = self.__computeNewCoordinatesVector3( attrArray[ i ], changeOfBasisMatrix )
+ else:
+ attrArrayNew[ i ] = self.__computeNewCoordinatesVector6( attrArray[ i ], changeOfBasisMatrix )
+
+ if not np.any( np.isfinite( attrArrayNew ) ):
+ self.logger.error( "Attribute new coordinate calculation failed." )
+ return attrArrayNew
+
+ def __computeNewCoordinatesVector3(
+ self: Self,
+ vector: npt.NDArray[ np.float64 ],
+ changeOfBasisMatrix: npt.NDArray[ np.float64 ],
+ ) -> npt.NDArray[ np.float64 ]:
+ """Compute attribute new coordinates of vector of size 3.
+
+ Args:
+ vector (npt.NDArray[np.float64]): input coordinates.
+ changeOfBasisMatrix (npt.NDArray[np.float64]): change of basis matrix
+
+ Returns:
+ npt.NDArray[np.float64]: new coordinates
+ """
+ return geom.computeCoordinatesInNewBasis( vector, changeOfBasisMatrix )
+
+ def __computeNewCoordinatesVector6(
+ self: Self,
+ vector: npt.NDArray[ np.float64 ],
+ changeOfBasisMatrix: npt.NDArray[ np.float64 ],
+ ) -> npt.NDArray[ np.float64 ]:
+ """Compute attribute new coordinates of vector of size > 3.
+
+ Args:
+ vector (npt.NDArray[np.float64]): input coordinates.
+ changeOfBasisMatrix (npt.NDArray[np.float64]): change of basis matrix
+
+ Returns:
+ npt.NDArray[np.float64]: new coordinates
+ """
+ attributeMatrix: npt.NDArray[ np.float64 ] = getAttributeMatrixFromVector( vector )
+ attributeMatrixNew: npt.NDArray[ np.float64 ] = np.full_like( attributeMatrix, np.nan )
+ # For each column of the matrix
+ for j in range( attributeMatrix.shape[ 1 ] ):
+ attributeMatrixNew[ :, j ] = geom.computeCoordinatesInNewBasis( attributeMatrix[ :, j ],
+ changeOfBasisMatrix )
+ return getAttributeVectorFromMatrix( attributeMatrixNew, vector.size )
+
+ def __computeChangeOfBasisMatrix( self: Self, localBasis: npt.NDArray[ np.float64 ],
+ fromLocalToYXZ: bool ) -> npt.NDArray[ np.float64 ]:
+ """Compute the change of basis matrix according to local coordinates.
+
+ Args:
+ localBasis (npt.NDArray[np.float64]): local coordinate vectors.
+ fromLocalToYXZ (bool): if True, change of basis matrix is from local
+ to XYZ bases, otherwise it is from XYZ to local bases.
+
+ Returns:
+ npt.NDArray[np.float64]: change of basis matrix.
+ """
+ P: npt.NDArray[ np.float64 ] = np.transpose( localBasis )
+ if fromLocalToYXZ:
+ return P
+ # Inverse the change of basis matrix
+ return np.linalg.inv( P ).astype( np.float64 )
+
+ def getNormalTangentsVectors( self: Self ) -> npt.NDArray[ np.float64 ]:
+ """Compute the change of basis matrix from Local to XYZ bases.
+
+ Returns:
+ npt.NDArray[np.float64]: Nx3 matrix of local vector coordinates.
+ """
+ # Get normal and first tangent components
+ normals: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy(
+ self.outputMesh.GetCellData().GetNormals() ) # type: ignore[no-untyped-call]
+ if normals is None:
+ self.logger.error( "Normal attribute was not found." )
+ tangents1: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy(
+ self.outputMesh.GetCellData().GetTangents() ) # type: ignore[no-untyped-call]
+ if tangents1 is None:
+ self.logger.error( "Tangents attribute was not found." )
+
+ # Compute second tangential component
+ tangents2: npt.NDArray[ np.float64 ] = np.cross( normals, tangents1, axis=1 ).astype( np.float64 )
+ if tangents2 is None:
+ self.logger.error( "Local basis third axis was not computed." )
+
+ # Put vectors as columns
+ return np.array( ( normals, tangents1, tangents2 ) )
+
+ def computeShearCapacityUtilization( self: Self ) -> bool:
+ """Compute the shear capacity utilization (SCU) on surface.
+
+ Returns:
+ bool: True if calculation successfully ended, False otherwise.
+ """
+ SCUAttributeName: str = PostProcessingOutputsEnum.SCU.attributeName
+
+ if not isAttributeInObject( self.outputMesh, SCUAttributeName, self.attributeOnPoints ):
+ tractionAttributeName: str = GeosMeshOutputsEnum.TRACTION.attributeName
+ traction: npt.NDArray[ np.float64 ] = getArrayInObject( self.outputMesh, tractionAttributeName,
+ self.attributeOnPoints )
+ if traction is None:
+ self.logger.error( f"{tractionAttributeName} attribute is undefined." )
+ self.logger.error( f"Failed to compute {SCUAttributeName}." )
+
+ scuAttribute: npt.NDArray[ np.float64 ] = fcts.shearCapacityUtilization(
+ traction, self.rockCohesion, self.frictionAngle )
+
+ # Create attribute
+ if not createAttribute(
+ self.outputMesh,
+ scuAttribute,
+ SCUAttributeName,
+ (),
+ self.attributeOnPoints,
+ logger = self.logger
+ ):
+ self.logger.error( f"Failed to create attribute {SCUAttributeName}." )
+ return False
+ else:
+ self.logger.info( f"SCU computed and added to the output mesh." )
+ self.newAttributeNames.add( SCUAttributeName )
+
+ return True
+
+ def GetOutputMesh( self: Self ) -> vtkPolyData:
+ """Get the output mesh with computed attributes.
+
+ Returns:
+ vtkPolyData: The surfacic output mesh.
+ """
+ return self.outputMesh
diff --git a/geos-utils/src/geos/utils/GeosOutputsConstants.py b/geos-utils/src/geos/utils/GeosOutputsConstants.py
index 3e684aaac..c98b3fe89 100644
--- a/geos-utils/src/geos/utils/GeosOutputsConstants.py
+++ b/geos-utils/src/geos/utils/GeosOutputsConstants.py
@@ -306,8 +306,17 @@ def getAttributeToConvertFromLocalToXYZ() -> set[ str ]:
"""Get the list of attribute names to convert from local to xyz basis.
Returns:
- list[str]: list of attributes to convert
+ list[str]: set of attributes to convert
"""
return {
GeosMeshOutputsEnum.DISPLACEMENT_JUMP.attributeName,
}
+
+def getAttributeToConvertFromXYZToLocal() -> set[ str ]:
+ """Get the list of attribute names to convert from canonical (XYZ) to local basis.
+ This list is empty at the moment.
+
+ Returns:
+ set[ str ]: set of attributes to convert
+ """
+ return set()
\ No newline at end of file
From 391aeea9d3671118a7f21189d64b05f3ddc784a1 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Tue, 9 Sep 2025 16:49:14 +0200
Subject: [PATCH 02/62] Move and refactor plugin
---
.../src/PVplugins/PVSurfaceGeomechanics.py | 250 ------------------
.../post_processing/SurfaceGeomechanics.py | 6 +
geos-pv/requirements.txt | 3 +-
.../geos/pv/plugins/PVSurfaceGeomechanics.py | 197 ++++++++++++++
4 files changed, 205 insertions(+), 251 deletions(-)
delete mode 100644 geos-posp/src/PVplugins/PVSurfaceGeomechanics.py
create mode 100644 geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
diff --git a/geos-posp/src/PVplugins/PVSurfaceGeomechanics.py b/geos-posp/src/PVplugins/PVSurfaceGeomechanics.py
deleted file mode 100644
index 0d0b7ed52..000000000
--- a/geos-posp/src/PVplugins/PVSurfaceGeomechanics.py
+++ /dev/null
@@ -1,250 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0
-# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Martin Lemay
-# ruff: noqa: E402 # disable Module level import not at top of file
-import os
-import sys
-
-import numpy as np
-from typing_extensions import Self
-
-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
-
-from geos.utils.Logger import Logger, getLogger
-from geos.utils.PhysicalConstants import (
- DEFAULT_FRICTION_ANGLE_DEG,
- DEFAULT_ROCK_COHESION,
-)
-from geos_posp.filters.SurfaceGeomechanics import SurfaceGeomechanics
-from geos.mesh.utils.multiblockHelpers import (
- getBlockElementIndexesFlatten,
- getBlockFromFlatIndex,
-)
-from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
- VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
-)
-from vtkmodules.vtkCommonCore import (
- vtkDataArray,
- vtkInformation,
- vtkInformationVector,
-)
-from vtkmodules.vtkCommonDataModel import (
- vtkDataObject,
- vtkMultiBlockDataSet,
- vtkPolyData,
-)
-
-__doc__ = r"""
-PVSurfaceGeomechanics is a Paraview plugin that allows to compute
-additional geomechanical attributes from the input surfaces.
-
-Input and output types are vtkMultiBlockDataSet.
-
-To use it:
-
-* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVSurfaceGeomechanics.
-* Select any pipeline child of the second ouput from
- GeosExtractMergeBlocksVolumeSurface* filter.
-* Search and Apply PVSurfaceGeomechanics Filter.
-
-"""
-
-
-@smproxy.filter( name="PVSurfaceGeomechanics", label="Geos Surface Geomechanics" )
-@smhint.xml( '' )
-@smproperty.input( name="Input", port_index=0 )
-@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
-class PVSurfaceGeomechanics( VTKPythonAlgorithmBase ):
-
- def __init__( self: Self ) -> None:
- """Paraview plugin to compute additional geomechanical surface outputs.
-
- Input is either a vtkMultiBlockDataSet that contains surfaces with
- Normals and Tangential attributes.
- """
- super().__init__(
- nInputPorts=1,
- nOutputPorts=1,
- inputType="vtkMultiBlockDataSet",
- outputType="vtkMultiBlockDataSet",
- )
-
- # rock cohesion (Pa)
- self.m_rockCohesion: float = DEFAULT_ROCK_COHESION
- # friction angle (°)
- self.m_frictionAngle: float = DEFAULT_FRICTION_ANGLE_DEG
- # logger
- self.m_logger: Logger = getLogger( "Surface Geomechanics Filter" )
-
- def SetLogger( self: Self, logger: Logger ) -> None:
- """Set filter logger.
-
- Args:
- logger (Logger): logger
- """
- self.m_logger = logger
-
- @smproperty.doublevector(
- name="RockCohesion",
- label="Rock Cohesion (Pa)",
- default_values=DEFAULT_ROCK_COHESION,
- 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 a01SetRockCohesion( self: Self, value: float ) -> None:
- """Set rock cohesion.
-
- Args:
- value (float): rock cohesion (Pa)
- """
- self.m_rockCohesion = value
- 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,
- 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 a02SetFrictionAngle( self: Self, value: float ) -> None:
- """Set frition angle.
-
- Args:
- value (float): friction angle (°)
- """
- self.m_frictionAngle = value
- self.Modified()
-
- def getFrictionAngle( self: Self ) -> float:
- """Get friction angle in radian.
-
- Returns:
- float: friction angle.
- """
- return self.m_frictionAngle * np.pi / 180.0
-
- 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(), "vtkMultiBlockDataSet" )
- return 1
-
- 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 RequestData(
- self: Self,
- request: vtkInformation, # noqa: F841
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestData.
-
- Args:
- request (vtkInformation): request
- inInfoVec (list[vtkInformationVector]): input objects
- outInfoVec (vtkInformationVector): output objects
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- self.m_logger.info( f"Apply filter {__name__}" )
- try:
- input0: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
- output: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
-
- assert input0 is not None, "Input Surface is null."
- assert output is not None, "Output pipeline is null."
-
- output.ShallowCopy( input0 )
- self.computeSurfaceGeomecanics( input0, output )
- output.Modified()
- mess: str = ( "Surface geomechanics attributes calculation successfully ended." )
- self.m_logger.info( mess )
- except AssertionError as e:
- mess1: str = "Surface geomechanics attributes calculation failed due to:"
- self.m_logger.error( mess1 )
- self.m_logger.error( e, exc_info=True )
- return 0
- except Exception as e:
- mess0: str = "Surface geomechanics attributes calculation failed due to:"
- self.m_logger.critical( mess0 )
- self.m_logger.critical( e, exc_info=True )
- return 0
- return 1
-
- def computeSurfaceGeomecanics( self: Self, input: vtkMultiBlockDataSet, output: vtkMultiBlockDataSet ) -> None:
- """Compute surface geomechanics new attributes.
-
- Args:
- input (vtkMultiBlockDataSet): input multiBlockDataSet
- output (vtkMultiBlockDataSet): output multiBlockDataSet
- """
- surfaceBlockIndexes: list[ int ] = getBlockElementIndexesFlatten( input )
- for blockIndex in surfaceBlockIndexes:
- surfaceBlock0: vtkDataObject = getBlockFromFlatIndex( output, blockIndex )
- assert surfaceBlock0 is not None, "Surface is undefined."
- surfaceBlock: vtkPolyData = vtkPolyData.SafeDownCast( surfaceBlock0 )
- filter: SurfaceGeomechanics = SurfaceGeomechanics()
- filter.AddInputDataObject( surfaceBlock )
- filter.SetRockCohesion( self.getRockCohesion() )
- filter.SetFrictionAngle( self.getFrictionAngle() )
- filter.Update()
- outputSurface: vtkPolyData = filter.GetOutputDataObject( 0 )
-
- # add attributes to output surface mesh
- for attributeName in filter.GetNewAttributeNames():
- attr: vtkDataArray = outputSurface.GetCellData().GetArray( attributeName )
- surfaceBlock.GetCellData().AddArray( attr )
- surfaceBlock.GetCellData().Modified()
- surfaceBlock.Modified()
- output.Modified()
diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
index f5e83be6f..30d3d6ecb 100644
--- a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
+++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
@@ -109,6 +109,8 @@ def __init__( self: Self,
self.logger.setLevel( logging.INFO )
# Input surfacic mesh
+ if surfacicMesh is None:
+ self.logger.error( "Input surface is undefined." )
if not surfacicMesh.IsA( "vtkPolyData" ):
self.logger.error( f" surfacicMesh parameter is expected to be a vtkPolyData, not a {type(surfacicMesh)}." )
self.inputMesh: vtkPolyData = surfacicMesh
@@ -178,10 +180,14 @@ def applyFilter( self: Self ) -> bool:
# Conversion of vectorial attributes from Normal/Tangent basis to xyz basis
if not self.convertLocalToXYZBasisAttributes():
self.logger.error( "Error while converting Local to XYZ basis attributes." )
+ return False
+
# Compute shear capacity utilization
if not self.computeShearCapacityUtilization():
self.logger.error( "Error while computing SCU." )
+ return False
+ self.logger.info( f"Filter {self.logger.name} succeeded." )
return True
def convertLocalToXYZBasisAttributes( self: Self ) -> bool:
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/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
new file mode 100644
index 000000000..756f2afb1
--- /dev/null
+++ b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
@@ -0,0 +1,197 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: Martin Lemay, Paloma Martinez
+# ruff: noqa: E402 # disable Module level import not at top of file
+import sys
+from pathlib import Path
+import numpy as np
+from typing_extensions import Self
+
+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,
+)
+
+
+# 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
+
+update_paths()
+
+from geos.utils.PhysicalConstants import (
+ DEFAULT_FRICTION_ANGLE_DEG,
+ DEFAULT_ROCK_COHESION,
+)
+from geos.processing.post_processing.SurfaceGeomechanics import SurfaceGeomechanics
+from geos.mesh.utils.multiblockHelpers import (
+ getBlockElementIndexesFlatten,
+ getBlockFromFlatIndex,
+)
+from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
+ VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
+)
+from vtkmodules.vtkCommonCore import (
+ vtkDataArray,
+ vtkInformation,
+ vtkInformationVector,
+)
+from vtkmodules.vtkCommonDataModel import (
+ vtkMultiBlockDataSet,
+ vtkPolyData,
+)
+
+__doc__ = """
+PVSurfaceGeomechanics is a Paraview plugin that allows to compute
+additional geomechanical attributes from the input surfaces, such as shear capacity utilization (SCU).
+
+Input and output are vtkMultiBlockDataSet.
+.. Important::
+ Please refer to the GeosExtractMergeBlockVolumeSurface* filters to provide the correct input.
+
+
+To use it:
+
+* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVSurfaceGeomechanics.
+* Select any pipeline child of the second ouput from
+ GeosExtractMergeBlocksVolumeSurface* filter.
+* Select Filters > 3-Geos Geomechanics > Geos Surface Geomechanics.
+* (Optional) Set rock cohesion and/or friction angle.
+* Apply.
+
+"""
+
+@smproxy.filter( name="PVSurfaceGeomechanics", label="Geos Surface Geomechanics" )
+@smhint.xml( '' )
+@smproperty.input( name="Input", port_index=0 )
+@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
+class PVSurfaceGeomechanics( VTKPythonAlgorithmBase ):
+
+ def __init__( self: Self ) -> None:
+ """Compute additional geomechanical surface outputs.
+
+ Input is a vtkMultiBlockDataSet that contains surfaces with
+ Normals and Tangential attributes.
+ """
+ super().__init__(
+ nInputPorts=1,
+ nOutputPorts=1,
+ inputType="vtkMultiBlockDataSet",
+ outputType="vtkMultiBlockDataSet",
+ )
+ # rock cohesion (Pa)
+ self.rockCohesion: float = DEFAULT_ROCK_COHESION
+ # friction angle (°)
+ self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_DEG
+
+ @smproperty.doublevector(
+ name="RockCohesion",
+ label="Rock Cohesion (Pa)",
+ default_values=DEFAULT_ROCK_COHESION,
+ 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 a01SetRockCohesion( self: Self, value: float ) -> None:
+ """Set rock cohesion.
+
+ Args:
+ value (float): rock cohesion (Pa)
+ """
+ self.rockCohesion = value
+ self.Modified()
+
+ @smproperty.doublevector(
+ name="FrictionAngle",
+ label="Friction Angle (°)",
+ default_values=DEFAULT_FRICTION_ANGLE_DEG,
+ 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 a02SetFrictionAngle( self: Self, value: float ) -> None:
+ """Set frition angle.
+
+ Args:
+ value (float): friction angle (°)
+ """
+ self.frictionAngle = value
+ self.Modified()
+
+ def RequestData(
+ self: Self,
+ request: vtkInformation, # noqa: F841
+ inInfoVec: list[ vtkInformationVector ],
+ outInfoVec: vtkInformationVector,
+ ) -> int:
+ """Inherited from VTKPythonAlgorithmBase::RequestData.
+
+ Args:
+ request (vtkInformation): Request
+ inInfoVec (list[vtkInformationVector]): Input objects
+ outInfoVec (vtkInformationVector): Output objects
+
+ Returns:
+ int: 1 if calculation successfully ended, 0 otherwise.
+ """
+ inputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
+ output: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
+
+ assert inputMesh is not None, "Input surface is null."
+ assert output is not None, "Output pipeline is null."
+
+ output.ShallowCopy( inputMesh )
+
+ surfaceBlockIndexes: list[ int ] = getBlockElementIndexesFlatten( inputMesh )
+ for blockIndex in surfaceBlockIndexes:
+ surfaceBlock: vtkPolyData = vtkPolyData.SafeDownCast( getBlockFromFlatIndex( output, blockIndex ) )
+
+ filter: SurfaceGeomechanics = SurfaceGeomechanics( surfaceBlock, True )
+
+ if not filter.logger.hasHandlers():
+ filter.SetLoggerHandler( VTKHandler() )
+
+ filter.SetRockCohesion( self._getRockCohesion() )
+ filter.SetFrictionAngle( self._getFrictionAngle() )
+ filter.applyFilter()
+
+ outputSurface: vtkPolyData = filter.GetOutputMesh()
+
+ # add attributes to output surface mesh
+ for attributeName in filter.GetNewAttributeNames():
+ attr: vtkDataArray = outputSurface.GetCellData().GetArray( attributeName )
+ surfaceBlock.GetCellData().AddArray( attr )
+ surfaceBlock.GetCellData().Modified()
+ surfaceBlock.Modified()
+
+ output.Modified()
+
+ # TODO : modify logger
+ return 1
+
+ def _getFrictionAngle( self: Self ) -> float:
+ """Get friction angle in radian.
+
+ Returns:
+ float: The friction angle.
+ """
+ return self.frictionAngle * np.pi / 180.0
+
+ def _getRockCohesion( self: Self ) -> float:
+ """Get rock cohesion.
+
+ Returns:
+ float: rock cohesion.
+ """
+ return self.rockCohesion
\ No newline at end of file
From 176b508791a494d8facee083a01b4f1842364dcb Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Wed, 10 Sep 2025 14:48:47 +0200
Subject: [PATCH 03/62] add geos-processing to install script
---
.../src/geos/processing/post_processing/SurfaceGeomechanics.py | 2 +-
install_packages.sh | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
index 30d3d6ecb..e9856bec8 100644
--- a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
+++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
@@ -112,7 +112,7 @@ def __init__( self: Self,
if surfacicMesh is None:
self.logger.error( "Input surface is undefined." )
if not surfacicMesh.IsA( "vtkPolyData" ):
- self.logger.error( f" surfacicMesh parameter is expected to be a vtkPolyData, not a {type(surfacicMesh)}." )
+ self.logger.error( f"Input surface is expected to be a vtkPolyData, not a {type(surfacicMesh)}." )
self.inputMesh: vtkPolyData = surfacicMesh
# Output surfacic mesh
self.outputMesh: vtkPolyData
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 8b72f3f79b4ed1ef76059d1c7d731010c5a61fcc Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Fri, 12 Sep 2025 11:31:51 +0200
Subject: [PATCH 04/62] Add more log
---
.../post_processing/SurfaceGeomechanics.py | 25 ++++++++++++++++---
.../geos/pv/plugins/PVSurfaceGeomechanics.py | 7 ++----
2 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
index e9856bec8..3f2f9f43a 100644
--- a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
+++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.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 enum import Enum
+from typing_extensions import Self
import geos.geomechanics.processing.geomechanicsCalculatorFunctions as fcts
import geos.utils.geometryFunctions as geom
import numpy as np
@@ -24,7 +24,7 @@
DEFAULT_FRICTION_ANGLE_RAD,
DEFAULT_ROCK_COHESION,
)
-from typing_extensions import Self
+
from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase
from vtkmodules.vtkCommonCore import (
vtkDataArray,
@@ -114,6 +114,8 @@ def __init__( self: Self,
if not surfacicMesh.IsA( "vtkPolyData" ):
self.logger.error( f"Input surface is expected to be a vtkPolyData, not a {type(surfacicMesh)}." )
self.inputMesh: vtkPolyData = surfacicMesh
+ # Identification of the input surface (logging purpose)
+ self.name = None
# Output surfacic mesh
self.outputMesh: vtkPolyData
@@ -142,6 +144,14 @@ def SetLoggerHandler( self: Self, handler: Logger ) -> None:
"The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization."
)
+ def SetSurfaceName( self: Self, name: str ):
+ """Set a name for the input surface. For logging purpose only.
+
+ Args:
+ name (str): The identifier for the surface.
+ """
+ self.name = name
+
def SetRockCohesion( self: Self, rockCohesion: float ) -> None:
"""Set rock cohesion value. Defaults to 0.0 Pa.
@@ -158,6 +168,7 @@ def SetFrictionAngle( self: Self, frictionAngle: float ) -> None:
"""
self.frictionAngle = frictionAngle
+
def GetNewAttributeNames( self: Self ) -> set[ str ]:
"""Get the set of new attribute names that were created.
@@ -172,7 +183,13 @@ def applyFilter( self: Self ) -> bool:
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
- self.logger.info( f"Applying filter { self.logger.name }." )
+ msg = f"Applying filter {self.logger.name}"
+ if self.name is not None:
+ msg += f" on surface : {self.name}."
+ else:
+ msg += "."
+
+ self.logger.info( msg )
self.outputMesh = vtkPolyData()
self.outputMesh.ShallowCopy( self.inputMesh )
@@ -242,7 +259,7 @@ def convertXYZToLocalBasisAttributes( self: Self ) -> bool:
"""Convert vectorial property coordinates from canonic to local basis.
Returns:
- bool: True if calculation successfully ended, False otherwise
+ bool: True if calculation successfully ended, False otherwise.
"""
# Look for the list of attributes to convert
basisFrom = "canonical"
diff --git a/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
index 756f2afb1..ff5256767 100644
--- a/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
+++ b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
@@ -14,7 +14,6 @@
VTKHandler,
)
-
# 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" ) )
@@ -158,7 +157,7 @@ def RequestData(
surfaceBlock: vtkPolyData = vtkPolyData.SafeDownCast( getBlockFromFlatIndex( output, blockIndex ) )
filter: SurfaceGeomechanics = SurfaceGeomechanics( surfaceBlock, True )
-
+ filter.SetSurfaceName( f"blockIndex {blockIndex}" )
if not filter.logger.hasHandlers():
filter.SetLoggerHandler( VTKHandler() )
@@ -176,8 +175,6 @@ def RequestData(
surfaceBlock.Modified()
output.Modified()
-
- # TODO : modify logger
return 1
def _getFrictionAngle( self: Self ) -> float:
@@ -194,4 +191,4 @@ def _getRockCohesion( self: Self ) -> float:
Returns:
float: rock cohesion.
"""
- return self.rockCohesion
\ No newline at end of file
+ return self.rockCohesion
From 5bd267c78d054757820e2dd9118d92a0358db960 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Thu, 18 Sep 2025 16:46:25 +0200
Subject: [PATCH 05/62] Add documentation
---
docs/geos-processing.rst | 14 ++++++++++++++
.../generic_processing_tool.rst | 4 ++++
docs/geos_processing_docs/post_processing.rst | 15 +++++++++++++++
docs/geos_processing_docs/pre_processing.rst | 4 ++++
docs/index.rst | 6 ++++--
5 files changed, 41 insertions(+), 2 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
diff --git a/docs/geos-processing.rst b/docs/geos-processing.rst
new file mode 100644
index 000000000..80f56429f
--- /dev/null
+++ b/docs/geos-processing.rst
@@ -0,0 +1,14 @@
+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..f23e4fea6
--- /dev/null
+++ b/docs/geos_processing_docs/post_processing.rst
@@ -0,0 +1,15 @@
+Post-processing filters
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This module contains GEOS post-processing tools.
+
+Geomechanics post-processing tools
+=====================================
+
+geos.processing.post_processing.SurfaceGeomechanics
+-------------------------------------------------------
+
+.. automodule:: geos.processing.post_processing.SurfaceGeomechanics
+ :members:
+ :undoc-members:
+ :show-inheritance:
\ 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
From 4277ebb1348005d2ea8e357893887d2e44c469e1 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Fri, 3 Oct 2025 11:15:54 +0200
Subject: [PATCH 06/62] Filter cleaning
---
docs/geos_processing_docs/post_processing.rst | 2 +-
.../post_processing/SurfaceGeomechanics.py | 281 +++++++++---------
.../geos/pv/plugins/PVSurfaceGeomechanics.py | 5 +-
.../src/geos/utils/GeosOutputsConstants.py | 22 +-
4 files changed, 152 insertions(+), 158 deletions(-)
diff --git a/docs/geos_processing_docs/post_processing.rst b/docs/geos_processing_docs/post_processing.rst
index f23e4fea6..45ea48579 100644
--- a/docs/geos_processing_docs/post_processing.rst
+++ b/docs/geos_processing_docs/post_processing.rst
@@ -12,4 +12,4 @@ geos.processing.post_processing.SurfaceGeomechanics
.. automodule:: geos.processing.post_processing.SurfaceGeomechanics
:members:
:undoc-members:
- :show-inheritance:
\ No newline at end of file
+ :show-inheritance:
diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
index 3f2f9f43a..0662ce24a 100644
--- a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
+++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
@@ -2,29 +2,13 @@
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Martin Lemay
# ruff: noqa: E402 # disable Module level import not at top of file
-from typing_extensions import Self
-import geos.geomechanics.processing.geomechanicsCalculatorFunctions as fcts
-import geos.utils.geometryFunctions as geom
+import logging
import numpy as np
+
+from typing_extensions import Self, Union
import numpy.typing as npt
-import vtkmodules.util.numpy_support as vnp
-from geos.utils.algebraFunctions import (
- getAttributeMatrixFromVector,
- getAttributeVectorFromMatrix,
-)
-from geos.utils.GeosOutputsConstants import (
- ComponentNameEnum,
- GeosMeshOutputsEnum,
- PostProcessingOutputsEnum,
- getAttributeToConvertFromLocalToXYZ,
- getAttributeToConvertFromXYZToLocal,
-)
-from geos.utils.Logger import logging, Logger, getLogger
-from geos.utils.PhysicalConstants import (
- DEFAULT_FRICTION_ANGLE_RAD,
- DEFAULT_ROCK_COHESION,
-)
+import vtkmodules.util.numpy_support as vnp
from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase
from vtkmodules.vtkCommonCore import (
vtkDataArray,
@@ -39,11 +23,35 @@
getAttributeSet,
isAttributeInObject,
)
-
+import geos.geomechanics.processing.geomechanicsCalculatorFunctions as fcts
+from geos.utils.Logger import ( Logger, getLogger )
+from geos.utils.PhysicalConstants import (
+ DEFAULT_FRICTION_ANGLE_RAD,
+ DEFAULT_ROCK_COHESION,
+)
+from geos.utils.algebraFunctions import (
+ getAttributeMatrixFromVector,
+ getAttributeVectorFromMatrix,
+)
+from geos.utils.GeosOutputsConstants import (
+ ComponentNameEnum,
+ GeosMeshOutputsEnum,
+ PostProcessingOutputsEnum,
+)
+import geos.utils.geometryFunctions as geom
__doc__ = """
-SurfaceGeomechanics vtk filter allows to compute Geomechanical
-properties on surfaces.
+SurfaceGeomechanics is a VTK filter that allows:
+ - Conversion of a set of attributes from local basis to XYZ basis
+ - Computation of the shear capacity utilization (SCU)
+
+.. Warning::
+ The computation of the SCU requires the presence of a 'traction' attribute in the input mesh.
+
+.. Note::
+ Default values for physical constants used in this filter:
+ - rock cohesion: 0.0 Pa ( fractured case)
+ - friction angle: 10°
Filter input and output types are vtkPolyData.
@@ -68,7 +76,7 @@
yourHandler: logging.Handler
filter.SetLoggerHandler( yourHandler )
- # Set rock cohesion and friction angle (optional)
+ # [optional] Set rock cohesion and friction angle
filter.SetRockCohesion( rockCohesion )
filter.SetFrictionAngle( frictionAngle )
@@ -80,6 +88,14 @@
# Get created attribute names
newAttributeNames: set[str] = filter.GetNewAttributeNames()
+
+
+.. Note::
+ By default, conversion of attributes from local to XYZ basis is performed for the following list: { 'displacementJump' }.
+ This list can be modified in different ways:
+ - Addition of one or several additional attributes to the set by using the filter function `AddAttributesToConvert`.
+ - Replace the list completely with the function `SetAttributesToConvert`.
+ Note that the dimension of the attributes to convert must be equal or greater than 3.
"""
loggerTitle: str = "Surface Geomechanics"
@@ -118,6 +134,9 @@ def __init__( self: Self,
self.name = None
# Output surfacic mesh
self.outputMesh: vtkPolyData
+ # Default attributes to convert from local to XYZ
+ self.convertAttributesOn: bool = True
+ self.attributesToConvert: set[ str ] = { GeosMeshOutputsEnum.DISPLACEMENT_JUMP.attributeName, }
# Attributes are either on points or on cells
self.attributeOnPoints: bool = False
@@ -161,7 +180,7 @@ def SetRockCohesion( self: Self, rockCohesion: float ) -> None:
self.rockCohesion = rockCohesion
def SetFrictionAngle( self: Self, frictionAngle: float ) -> None:
- """Set friction angle value. Defaults to 10 / 180 * pi rad.
+ """Set friction angle value in radians. Defaults to 10 / 180 * pi rad.
Args:
frictionAngle (float): The friction angle in radians.
@@ -169,6 +188,58 @@ def SetFrictionAngle( self: Self, frictionAngle: float ) -> None:
self.frictionAngle = frictionAngle
+ def ConvertAttributesOn( self: Self ) -> None:
+ """Activate the conversion of attributes from local to XYZ basis."""
+ self.convertAttributesOn = True
+
+
+ def ConvertAttributesOff( self: Self ) -> None:
+ """Deactivate the conversion of attributes from local to XYZ bais."""
+ self.convertAttributesOn = False
+
+
+ def GetConvertAttributes( self: Self ) -> bool:
+ """If convertAttributesOn is True, the set of attributes will be converted from local to XYZ during filter application. Default is True.
+
+ Returns:
+ bool: Current state of the attributes conversion request.
+ """
+ return self.convertAttributesOn
+
+
+ def GetAttributesToConvert( self: Self ) -> set[ str ]:
+ """Get the set of attributes that will be converted from local to XYZ basis.
+
+ Returns:
+ set[ str ]: The set of attributes that should be converted.
+ """
+ return self.attributesToConvert
+
+
+ def SetAttributesToConvert( self: Self, attributesToConvert: set[ str ] ) -> None:
+ """Set the list of attributes that will be converted from local to XYZ basis.
+
+ Args:
+ attributesToConvert (set[str]): The set of attributes names that will be converted from local to XYZ basis
+ """
+ self.attributesToConvert = attributesToConvert
+ if len( self.attributesToConvert ) != 0:
+ self.ConvertAttributesOn()
+ else:
+ self.ConvertAttributesOff()
+ self.logger.warning( "Empty set of attributes to convert." )
+
+
+ def AddAttributesToConvert( self: Self, attributeName: Union[ list[ str ], set[ str ] ] ) -> None:
+ """Add an attribute to the set of attributes to convert.
+
+ Args:
+ attributeName (Union[ list[str],set[ str]]): List of the attribute array names.
+ """
+ self.attributesToConvert.add( attributeName )
+ self.ConvertAttributesOn()
+
+
def GetNewAttributeNames( self: Self ) -> set[ str ]:
"""Get the set of new attribute names that were created.
@@ -177,6 +248,7 @@ def GetNewAttributeNames( self: Self ) -> set[ str ]:
"""
return self.newAttributeNames
+
def applyFilter( self: Self ) -> bool:
"""Compute Geomechanical properties on input surface.
@@ -194,10 +266,12 @@ def applyFilter( self: Self ) -> bool:
self.outputMesh = vtkPolyData()
self.outputMesh.ShallowCopy( self.inputMesh )
- # Conversion of vectorial attributes from Normal/Tangent basis to xyz basis
- if not self.convertLocalToXYZBasisAttributes():
- self.logger.error( "Error while converting Local to XYZ basis attributes." )
- return False
+ # Conversion of attributes from Normal/Tangent basis to xyz basis
+ if self.convertAttributesOn:
+ self.logger.info( "Conversion of attributes from local to XYZ basis.")
+ if not self._convertAttributesFromLocalToXYZBasis():
+ self.logger.error( "Error while converting attributes from local to XYZ basis." )
+ return False
# Compute shear capacity utilization
if not self.computeShearCapacityUtilization():
@@ -207,138 +281,75 @@ def applyFilter( self: Self ) -> bool:
self.logger.info( f"Filter {self.logger.name} succeeded." )
return True
- def convertLocalToXYZBasisAttributes( self: Self ) -> bool:
- """Convert vectorial property coordinates from local to canonic basis.
+
+ def _convertAttributesFromLocalToXYZBasis( self: Self ) -> bool:
+ """Convert attributes from local to XYZ basis.
Returns:
- bool: True if calculation successfully ended, False otherwise
+ bool: True if calculation successfully ended or no attributes, False otherwise
"""
- # Look for the list of attributes to convert
- basisFrom = "local"
- basisTo = "XYZ"
- attributesToConvert: set[ str ] = self.__getAttributesToConvert( basisFrom, basisTo )
+ # Get the list of attributes to convert and filter
+ attributesToConvert: set[ str ] = self.__filterAttributesToConvert( self.attributesToConvert )
+
if len( attributesToConvert ) == 0:
- self.logger.error( f"No attribute to convert from {basisFrom} to {basisTo} basis were found." )
- return False
+ self.logger.warning( f"No attribute to convert from local to XYZ basis were found." )
+ return True
# Get local coordinate vectors
- normalTangentVectors: npt.NDArray[ np.float64 ] = self.getNormalTangentsVectors()
- # Do conversion from local to canonic for each attribute
- for attrName in attributesToConvert:
+ normalTangentVectors: npt.NDArray[ np.float64 ] = self.__getNormalTangentsVectors()
+ # Do conversion from local to XYZ for each attribute
+ for attrNameLocal in attributesToConvert:
# Skip attribute if it is already in the object
- newAttrName: str = attrName + "_" + ComponentNameEnum.XYZ.name
- if isAttributeInObject( self.outputMesh, newAttrName, False ):
+ attrNameXYZ: str = f" {attrNameLocal}_{ComponentNameEnum.XYZ.name}"
+ if isAttributeInObject( self.outputMesh, attrNameXYZ, self.attributeOnPoints ):
continue
- attr: vtkDoubleArray = self.outputMesh.GetCellData().GetArray( attrName )
- if attr is None:
- self.logger.error( f"Attribute {attrName} is undefined." )
- if not attr.GetNumberOfComponents() > 2:
- self.logger.error( f"Dimension of the attribute {attrName} must be equal or greater than 3." )
+ attrArray: vtkDoubleArray = getArrayInObject( self.outputMesh, attrNameLocal, self.attributeOnPoints)
- attrArray: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy( attr ) # type: ignore[no-untyped-call]
- newAttrArray: npt.NDArray[ np.float64 ] = self.computeNewCoordinates( attrArray, normalTangentVectors,
+ newAttrArray: npt.NDArray[ np.float64 ] = self.__computeNewCoordinates( attrArray, normalTangentVectors,
True )
# create attribute
- if not createAttribute(
+ if createAttribute(
self.outputMesh,
newAttrArray,
- newAttrName,
+ attrNameXYZ,
ComponentNameEnum.XYZ.value,
- False,
- logger = self.logger
- ):
- self.logger.error( f"Failed to create attribute {newAttrName}." )
- else:
- self.logger.info( f"Attribute {newAttrName} added to the output mesh." )
- self.newAttributeNames.add( newAttrName )
- return True
-
- def convertXYZToLocalBasisAttributes( self: Self ) -> bool:
- """Convert vectorial property coordinates from canonic to local basis.
-
- Returns:
- bool: True if calculation successfully ended, False otherwise.
- """
- # Look for the list of attributes to convert
- basisFrom = "canonical"
- basisTo = "local"
- attributesToConvert: set[ str ] = self.__getAttributesToConvert( basisFrom, basisTo )
- if len( attributesToConvert ) == 0:
- self.logger.error( "No attribute to convert from canonic to local basis were found." )
- return False
-
- # get local coordinate vectors
- normalTangentVectors: npt.NDArray[ np.float64 ] = self.getNormalTangentsVectors()
- # Do conversion from canonic to local for each attribute
- for attrName in attributesToConvert:
- # Skip attribute if it is already in the object
- newAttrName: str = attrName + "_" + ComponentNameEnum.NORMAL_TANGENTS.name
- if isAttributeInObject( self.outputMesh, newAttrName, False ):
- continue
+ onPoints = self.attributeOnPoints,
+ logger = self.logger):
+ self.logger.info( f"Attribute {attrNameXYZ} added to the output mesh." )
+ self.newAttributeNames.add( attrNameXYZ )
- attr: vtkDoubleArray = self.outputMesh.GetCellData().GetArray( attrName )
- if attr is None:
- self.logger.error( f"Attribute {attrName} is undefined." )
- if not attr.GetNumberOfComponents() > 2:
- self.logger.error( f"Dimension of the attribute {attrName} must be equal or greater than 3." )
-
- attrArray: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy( attr ) # type: ignore[no-untyped-call]
- newAttrArray: npt.NDArray[ np.float64 ] = self.computeNewCoordinates( attrArray, normalTangentVectors,
- False )
-
- # Create attribute
- if not createAttribute(
- self.outputMesh,
- newAttrArray,
- newAttrName,
- ComponentNameEnum.NORMAL_TANGENTS.value,
- False,
- logger = self.logger
- ):
- self.logger.error( f"Failed to create attribute {newAttrName}." )
- return False
- else:
- self.logger.info( f"Attribute {newAttrName} added to the output mesh." )
- self.newAttributeNames.add( newAttrName )
return True
- def __getAttributesToConvert( self: Self, basisFrom: str, basisTo: str ):
- """Get the list of attributes to convert from one basis to another.
- Choices of basis are "local" or "XYZ".
-
- Returns:
- set[ str ]: Set of the attributes names
- """
- if basisFrom == "local" and basisTo == "XYZ":
- return getAttributeToConvertFromLocalToXYZ()
- elif basisFrom == "XYZ" and basisTo == "local":
- return getAttributeToConvertFromXYZToLocal()
- else:
- self.logger.error( f"Unknow basis named {basisFrom} or {basisTo}." )
-
-
- def filterAttributesToConvert( self: Self, attributesToFilter: set[ str ] ) -> set[ str ]:
+ def __filterAttributesToConvert( self: Self ) -> set[ str ]:
"""Filter the set of attribute names if they are vectorial and present.
- Args:
- attributesToFilter (set[str]): set of attribute names to filter.
-
Returns:
set[str]: Set of the attribute names.
"""
attributesFiltered: set[ str ] = set()
- attributeSet: set[ str ] = getAttributeSet( self.outputMesh, False )
- for attrName in attributesToFilter:
- if attrName in attributeSet:
- attr: vtkDataArray = self.outputMesh.GetCellData().GetArray( attrName )
- if attr.GetNumberOfComponents() > 2:
- attributesFiltered.add( attrName )
+
+ if len( self.attributesToConvert ) != 0:
+ attributeSet: set[ str ] = getAttributeSet( self.outputMesh, False )
+ for attrName in self.attributesToConvert:
+ if attrName in attributeSet:
+ attr: vtkDataArray = self.outputMesh.GetCellData().GetArray( attrName )
+ if attr.GetNumberOfComponents() > 2:
+ attributesFiltered.add( attrName )
+ else:
+ self.logger.warning( f"Attribute {attrName} filtered out. Dimension of the attribute must be equal or greater than 3." )
+ else:
+ self.logger.warning( f"Attribute {attrName} not in the input mesh.")
+
+ if len( attributesFiltered ) == 0:
+ self.logger.warning( "All attributes filtered out." )
+ self.ConvertAttributesOff()
+
return attributesFiltered
- def computeNewCoordinates(
+ def __computeNewCoordinates(
self: Self,
attrArray: npt.NDArray[ np.float64 ],
normalTangentVectors: npt.NDArray[ np.float64 ],
@@ -427,7 +438,7 @@ def __computeChangeOfBasisMatrix( self: Self, localBasis: npt.NDArray[ np.float6
# Inverse the change of basis matrix
return np.linalg.inv( P ).astype( np.float64 )
- def getNormalTangentsVectors( self: Self ) -> npt.NDArray[ np.float64 ]:
+ def __getNormalTangentsVectors( self: Self ) -> npt.NDArray[ np.float64 ]:
"""Compute the change of basis matrix from Local to XYZ bases.
Returns:
@@ -460,6 +471,7 @@ def computeShearCapacityUtilization( self: Self ) -> bool:
SCUAttributeName: str = PostProcessingOutputsEnum.SCU.attributeName
if not isAttributeInObject( self.outputMesh, SCUAttributeName, self.attributeOnPoints ):
+ # Get the traction to compute the SCU
tractionAttributeName: str = GeosMeshOutputsEnum.TRACTION.attributeName
traction: npt.NDArray[ np.float64 ] = getArrayInObject( self.outputMesh, tractionAttributeName,
self.attributeOnPoints )
@@ -467,6 +479,7 @@ def computeShearCapacityUtilization( self: Self ) -> bool:
self.logger.error( f"{tractionAttributeName} attribute is undefined." )
self.logger.error( f"Failed to compute {SCUAttributeName}." )
+ # Computation of the shear capacity utilization (SCU)
scuAttribute: npt.NDArray[ np.float64 ] = fcts.shearCapacityUtilization(
traction, self.rockCohesion, self.frictionAngle )
diff --git a/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
index ff5256767..ff198ba2d 100644
--- a/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
+++ b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
@@ -49,7 +49,8 @@
Input and output are vtkMultiBlockDataSet.
.. Important::
- Please refer to the GeosExtractMergeBlockVolumeSurface* filters to provide the correct input.
+ - Please refer to the GeosExtractMergeBlockVolumeSurface* filters to provide the correct input.
+ - This filter only works on triangles at the moment. Please apply a triangulation algorithm beforehand if required.
To use it:
@@ -57,7 +58,7 @@
* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVSurfaceGeomechanics.
* Select any pipeline child of the second ouput from
GeosExtractMergeBlocksVolumeSurface* filter.
-* Select Filters > 3-Geos Geomechanics > Geos Surface Geomechanics.
+* Select Filters > 3- Geos Geomechanics > Geos Surface Geomechanics.
* (Optional) Set rock cohesion and/or friction angle.
* Apply.
diff --git a/geos-utils/src/geos/utils/GeosOutputsConstants.py b/geos-utils/src/geos/utils/GeosOutputsConstants.py
index c98b3fe89..1ad99fbfc 100644
--- a/geos-utils/src/geos/utils/GeosOutputsConstants.py
+++ b/geos-utils/src/geos/utils/GeosOutputsConstants.py
@@ -299,24 +299,4 @@ def getAttributeToTransferFromInitialTime() -> dict[ str, str ]:
PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.attributeName,
PostProcessingOutputsEnum.POISSON_RATIO.attributeName:
PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.attributeName,
- }
-
-
-def getAttributeToConvertFromLocalToXYZ() -> set[ str ]:
- """Get the list of attribute names to convert from local to xyz basis.
-
- Returns:
- list[str]: set of attributes to convert
- """
- return {
- GeosMeshOutputsEnum.DISPLACEMENT_JUMP.attributeName,
- }
-
-def getAttributeToConvertFromXYZToLocal() -> set[ str ]:
- """Get the list of attribute names to convert from canonical (XYZ) to local basis.
- This list is empty at the moment.
-
- Returns:
- set[ str ]: set of attributes to convert
- """
- return set()
\ No newline at end of file
+ }
\ No newline at end of file
From 2e715381b0dcfde2c5ba2a05c9a3c95e947e9493 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Fri, 3 Oct 2025 11:36:52 +0200
Subject: [PATCH 07/62] Documentation
---
docs/geos_processing_docs/post_processing.rst | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/docs/geos_processing_docs/post_processing.rst b/docs/geos_processing_docs/post_processing.rst
index 45ea48579..839f4a7eb 100644
--- a/docs/geos_processing_docs/post_processing.rst
+++ b/docs/geos_processing_docs/post_processing.rst
@@ -6,6 +6,17 @@ This module contains GEOS post-processing tools.
Geomechanics post-processing tools
=====================================
+GEOS computes many outputs including flow and geomechanic properties if coupled flow geomechanics simulations were run. Users however need additional metrics to asses geomechanical stability as part of operational studies. Two filters have been developped to compute these additional metrics: `Geos Geomechanics Calculator`` and `Geos Surfacic Geomechanics`. In addition, a third filter allows to plot Mohr's circles on selected cells and time steps.
+
+.. Note::
+
+ Several processing filters require the definition of physical parameters. The following list is non-exhaustive.
+
+ Default values:
+ - rock cohesion: 0.0 Pa $( fractured case )
+ - friction angle: 10°
+
+
geos.processing.post_processing.SurfaceGeomechanics
-------------------------------------------------------
From b6f9d859f0b713c469ce3f52a5801ab51d8c5615 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Wed, 15 Oct 2025 16:50:04 +0200
Subject: [PATCH 08/62] Refactor the basis change computation
---
.../src/geos/mesh/utils/genericHelpers.py | 83 +++++++++-
.../post_processing/SurfaceGeomechanics.py | 148 ++++--------------
geos-utils/src/geos/utils/algebraFunctions.py | 98 ++++++++++--
3 files changed, 201 insertions(+), 128 deletions(-)
diff --git a/geos-mesh/src/geos/mesh/utils/genericHelpers.py b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
index 481b391ff..57ee6b22d 100644
--- a/geos-mesh/src/geos/mesh/utils/genericHelpers.py
+++ b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
@@ -4,12 +4,22 @@
import numpy as np
import numpy.typing as npt
from typing import Iterator, List, Sequence, Any, Union
-from vtkmodules.util.numpy_support import numpy_to_vtk
+from vtkmodules.util.numpy_support import (
+ numpy_to_vtk,
+ vtk_to_numpy
+)
from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints, reference
from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, vtkMultiBlockDataSet, vtkPolyData, vtkDataSet, vtkDataObject, vtkPlane, vtkCellTypes, vtkIncrementalOctreePointLocator
from vtkmodules.vtkFiltersCore import vtk3DLinearGridPlaneCutter
from geos.mesh.utils.multiblockHelpers import ( getBlockElementIndexesFlatten, getBlockFromFlatIndex )
+from geos.utils.algebraFunctions import (
+ getAttributeMatrixFromVector,
+ getAttributeVectorFromMatrix,
+ getLocalToXYZTransformMatrix,
+)
+
+
__doc__ = """
Generic VTK utilities.
@@ -267,3 +277,74 @@ def createVertices( cellPtsCoord: list[ npt.NDArray[ np.float64 ] ],
cellVertexMap += [ ptId.get() ] # type: ignore
cellVertexMapAll += [ tuple( cellVertexMap ) ]
return points, cellVertexMapAll
+
+
+def convertAttributeFromLocalToXYZForOneCell( vector, localBasisVectors ) -> npt.NDArray[ np.float64 ]:
+ """
+ Convert one cell attribute from local to XYZ basis.
+
+ .. Warning::
+ Vectors components are considered to be in GEOS output order such that \
+ v = ( M00, M11, M22, M12, M02, M01, M21, M20, M10 )
+
+ Args:
+ vector (npt.NDArray[np.float64]): The attribute expressed in local basis. The size of the vector must be 3, 9 or 6 (symmetrical case)
+ localBasisVectors (np.NDArray[np.float64]): The local basis vectors expressed in the XYZ basis.
+
+ Returns:
+ vectorXYZ (npt.NDArray[np.float64]): The attribute expressed in XYZ basis.
+ """
+ # Get components matrix from GEOS attribute vector
+ matrix3x3: npt.NDArray[ np.float64 ] = getAttributeMatrixFromVector( vector )
+
+ # Get transform matrix
+ transformMatrix: npt.NDArray[ np.float64 ] = getLocalToXYZTransformMatrix( localBasisVectors )
+
+ # Apply transformation
+ arrayXYZ: npt.NDArray[ np.float64 ] = transformMatrix @ matrix3x3 @ transformMatrix.T
+
+ # Convert back to GEOS type attribute
+ vectorXYZ: npt.NDArray[ np.float64 ] = getAttributeVectorFromMatrix( arrayXYZ, vector.size )
+
+ return vectorXYZ
+
+
+def getNormalVectors( dataset, ) -> npt.NDArray[ np.float64 ]:
+ normals: npt.NDArray[ np.float64 ] = vtk_to_numpy(
+ dataset.GetCellData().GetNormals() ) # type: ignore[no-untyped-call]
+ # TODO: if normals not defined, compute it on the fly
+ if normals is None:
+ raise ValueError( "Normal attribute was not found." )
+
+ return normals
+
+
+def getTangentsVectors( dataset, normals = None ) -> npt.NDArray[ np.float64 ]:
+ # Get first tangential component
+ tangents1: npt.NDArray[ np.float64 ] = vtk_to_numpy(
+ dataset.GetCellData().GetTangents() ) # type: ignore[no-untyped-call]
+ if tangents1 is None:
+ raise ValueError( "Tangents attribute was not found." )
+
+ # Compute second tangential component
+ if normals is None:
+ normals = getNormalVectors( dataset )
+
+ tangents2: npt.NDArray[ np.float64 ] = np.cross( normals, tangents1, axis=1 ).astype( np.float64 )
+ if tangents2 is None:
+ raise ValueError( "Local basis third axis was not computed." )
+
+ return ( tangents1, tangents2 )
+
+
+def getLocalBasisVectors( dataset: vtkPolyData ) -> npt.NDArray[ np.float64 ]:
+ """Return the local basis vectors for all cells of the input dataset.
+
+ Args:
+ dataset(vtkPolydata): The input dataset
+
+ """
+ normals = getNormalVectors( dataset )
+ tangents = getTangentsVectors( dataset, normals=normals )
+
+ return np.array( ( normals, *tangents ) )
diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
index 0662ce24a..c5dba4d9e 100644
--- a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
+++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
@@ -23,16 +23,16 @@
getAttributeSet,
isAttributeInObject,
)
+from geos.mesh.utils.genericHelpers import (
+ getLocalBasisVectors,
+ convertAttributeFromLocalToXYZForOneCell,
+)
import geos.geomechanics.processing.geomechanicsCalculatorFunctions as fcts
from geos.utils.Logger import ( Logger, getLogger )
from geos.utils.PhysicalConstants import (
DEFAULT_FRICTION_ANGLE_RAD,
DEFAULT_ROCK_COHESION,
)
-from geos.utils.algebraFunctions import (
- getAttributeMatrixFromVector,
- getAttributeVectorFromMatrix,
-)
from geos.utils.GeosOutputsConstants import (
ComponentNameEnum,
GeosMeshOutputsEnum,
@@ -46,7 +46,8 @@
- Computation of the shear capacity utilization (SCU)
.. Warning::
- The computation of the SCU requires the presence of a 'traction' attribute in the input mesh.
+ - The computation of the SCU requires the presence of a 'traction' attribute in the input mesh.
+ - Conversion from local to XYZ basis is currently only handled for cell attributes.
.. Note::
Default values for physical constants used in this filter:
@@ -269,7 +270,7 @@ def applyFilter( self: Self ) -> bool:
# Conversion of attributes from Normal/Tangent basis to xyz basis
if self.convertAttributesOn:
self.logger.info( "Conversion of attributes from local to XYZ basis.")
- if not self._convertAttributesFromLocalToXYZBasis():
+ if not self.convertAttributesFromLocalToXYZBasis():
self.logger.error( "Error while converting attributes from local to XYZ basis." )
return False
@@ -278,41 +279,41 @@ def applyFilter( self: Self ) -> bool:
self.logger.error( "Error while computing SCU." )
return False
- self.logger.info( f"Filter {self.logger.name} succeeded." )
+ self.logger.info( f"Filter {self.logger.name} successfully applied on surface {self.name}." )
return True
- def _convertAttributesFromLocalToXYZBasis( self: Self ) -> bool:
+ def convertAttributesFromLocalToXYZBasis( self: Self ) -> bool:
"""Convert attributes from local to XYZ basis.
Returns:
- bool: True if calculation successfully ended or no attributes, False otherwise
+ bool: True if calculation successfully ended or no attributes, False otherwise.
"""
# Get the list of attributes to convert and filter
- attributesToConvert: set[ str ] = self.__filterAttributesToConvert( self.attributesToConvert )
+ attributesToConvert: set[ str ] = self.__filterAttributesToConvert()
if len( attributesToConvert ) == 0:
self.logger.warning( f"No attribute to convert from local to XYZ basis were found." )
return True
- # Get local coordinate vectors
- normalTangentVectors: npt.NDArray[ np.float64 ] = self.__getNormalTangentsVectors()
# Do conversion from local to XYZ for each attribute
for attrNameLocal in attributesToConvert:
+ attrNameXYZ: str = f"{attrNameLocal}_{ComponentNameEnum.XYZ.name}"
+
# Skip attribute if it is already in the object
- attrNameXYZ: str = f" {attrNameLocal}_{ComponentNameEnum.XYZ.name}"
if isAttributeInObject( self.outputMesh, attrNameXYZ, self.attributeOnPoints ):
continue
- attrArray: vtkDoubleArray = getArrayInObject( self.outputMesh, attrNameLocal, self.attributeOnPoints)
+ if self.attributeOnPoints:
+ self.logger.error( "This filter can only convert cell attributes from local to XYZ basis, not point attributes." )
+ localArray: vtkDoubleArray = getArrayInObject( self.outputMesh, attrNameLocal, self.attributeOnPoints)
- newAttrArray: npt.NDArray[ np.float64 ] = self.__computeNewCoordinates( attrArray, normalTangentVectors,
- True )
+ arrayXYZ: npt.NDArray[ np.float64 ] = self.__computeXYZCoordinates( localArray )
- # create attribute
+ # Create converted attribute array in dataset
if createAttribute(
self.outputMesh,
- newAttrArray,
+ arrayXYZ,
attrNameXYZ,
ComponentNameEnum.XYZ.value,
onPoints = self.attributeOnPoints,
@@ -349,118 +350,37 @@ def __filterAttributesToConvert( self: Self ) -> set[ str ]:
return attributesFiltered
- def __computeNewCoordinates(
+ # TODO: Adapt to handle point attributes.
+ def __computeXYZCoordinates(
self: Self,
attrArray: npt.NDArray[ np.float64 ],
- normalTangentVectors: npt.NDArray[ np.float64 ],
- fromLocalToYXZ: bool,
) -> npt.NDArray[ np.float64 ]:
- """Compute the coordinates of a vectorial attribute.
+ """Compute the XYZ coordinates of a vectorial attribute.
Args:
attrArray (npt.NDArray[np.float64]): vector of attribute values
- normalTangentVectors (npt.NDArray[np.float64]): 3xNx3 local vector
- coordinates
- fromLocalToYXZ (bool): if True, conversion is done from local to XYZ
- basis, otherwise conversion is done from XZY to Local basis.
Returns:
npt.NDArray[np.float64]: Vector of new coordinates of the attribute.
"""
- attrArrayNew = np.full_like( attrArray, np.nan )
- # For each cell
- for i in range( attrArray.shape[ 0 ] ):
- # Get the change of basis matrix
- localBasis: npt.NDArray[ np.float64 ] = normalTangentVectors[ :, i, : ]
- changeOfBasisMatrix = self.__computeChangeOfBasisMatrix( localBasis, fromLocalToYXZ )
- if attrArray.shape[ 1 ] == 3:
- attrArrayNew[ i ] = self.__computeNewCoordinatesVector3( attrArray[ i ], changeOfBasisMatrix )
- else:
- attrArrayNew[ i ] = self.__computeNewCoordinatesVector6( attrArray[ i ], changeOfBasisMatrix )
-
- if not np.any( np.isfinite( attrArrayNew ) ):
- self.logger.error( "Attribute new coordinate calculation failed." )
- return attrArrayNew
-
- def __computeNewCoordinatesVector3(
- self: Self,
- vector: npt.NDArray[ np.float64 ],
- changeOfBasisMatrix: npt.NDArray[ np.float64 ],
- ) -> npt.NDArray[ np.float64 ]:
- """Compute attribute new coordinates of vector of size 3.
-
- Args:
- vector (npt.NDArray[np.float64]): input coordinates.
- changeOfBasisMatrix (npt.NDArray[np.float64]): change of basis matrix
-
- Returns:
- npt.NDArray[np.float64]: new coordinates
- """
- return geom.computeCoordinatesInNewBasis( vector, changeOfBasisMatrix )
+ attrXYZ: npt.NDArray[ np.float64 ] = np.full_like( attrArray, np.nan )
- def __computeNewCoordinatesVector6(
- self: Self,
- vector: npt.NDArray[ np.float64 ],
- changeOfBasisMatrix: npt.NDArray[ np.float64 ],
- ) -> npt.NDArray[ np.float64 ]:
- """Compute attribute new coordinates of vector of size > 3.
+ # Get all local basis vectors
+ localBasis: npt.NDArray[ np.float64 ] = getLocalBasisVectors( self.outputMesh )
- Args:
- vector (npt.NDArray[np.float64]): input coordinates.
- changeOfBasisMatrix (npt.NDArray[np.float64]): change of basis matrix
+ for i, cellAttribute in enumerate( attrArray ):
+ if len( cellAttribute ) not in ( 3, 6, 9 ):
+ raise ValueError( f"Inconsistent number of components for attribute. Expected 3, 6 or 9 but got { len( cellAttribute.shape ) }." )
- Returns:
- npt.NDArray[np.float64]: new coordinates
- """
- attributeMatrix: npt.NDArray[ np.float64 ] = getAttributeMatrixFromVector( vector )
- attributeMatrixNew: npt.NDArray[ np.float64 ] = np.full_like( attributeMatrix, np.nan )
- # For each column of the matrix
- for j in range( attributeMatrix.shape[ 1 ] ):
- attributeMatrixNew[ :, j ] = geom.computeCoordinatesInNewBasis( attributeMatrix[ :, j ],
- changeOfBasisMatrix )
- return getAttributeVectorFromMatrix( attributeMatrixNew, vector.size )
-
- def __computeChangeOfBasisMatrix( self: Self, localBasis: npt.NDArray[ np.float64 ],
- fromLocalToYXZ: bool ) -> npt.NDArray[ np.float64 ]:
- """Compute the change of basis matrix according to local coordinates.
+ # Compute attribute XYZ components
+ cellLocalBasis: npt.NDArray[ np.float64 ] = localBasis[ :, i, : ]
+ attrXYZ[ i ] = convertAttributeFromLocalToXYZForOneCell( cellAttribute, cellLocalBasis )
- Args:
- localBasis (npt.NDArray[np.float64]): local coordinate vectors.
- fromLocalToYXZ (bool): if True, change of basis matrix is from local
- to XYZ bases, otherwise it is from XYZ to local bases.
-
- Returns:
- npt.NDArray[np.float64]: change of basis matrix.
- """
- P: npt.NDArray[ np.float64 ] = np.transpose( localBasis )
- if fromLocalToYXZ:
- return P
- # Inverse the change of basis matrix
- return np.linalg.inv( P ).astype( np.float64 )
+ if not np.any( np.isfinite( attrXYZ ) ):
+ self.logger.error( "Attribute new coordinate calculation failed." )
- def __getNormalTangentsVectors( self: Self ) -> npt.NDArray[ np.float64 ]:
- """Compute the change of basis matrix from Local to XYZ bases.
+ return attrXYZ
- Returns:
- npt.NDArray[np.float64]: Nx3 matrix of local vector coordinates.
- """
- # Get normal and first tangent components
- normals: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy(
- self.outputMesh.GetCellData().GetNormals() ) # type: ignore[no-untyped-call]
- if normals is None:
- self.logger.error( "Normal attribute was not found." )
- tangents1: npt.NDArray[ np.float64 ] = vnp.vtk_to_numpy(
- self.outputMesh.GetCellData().GetTangents() ) # type: ignore[no-untyped-call]
- if tangents1 is None:
- self.logger.error( "Tangents attribute was not found." )
-
- # Compute second tangential component
- tangents2: npt.NDArray[ np.float64 ] = np.cross( normals, tangents1, axis=1 ).astype( np.float64 )
- if tangents2 is None:
- self.logger.error( "Local basis third axis was not computed." )
-
- # Put vectors as columns
- return np.array( ( normals, tangents1, tangents2 ) )
def computeShearCapacityUtilization( self: Self ) -> bool:
"""Compute the shear capacity utilization (SCU) on surface.
diff --git a/geos-utils/src/geos/utils/algebraFunctions.py b/geos-utils/src/geos/utils/algebraFunctions.py
index eeeb88e44..0afb84cd9 100644
--- a/geos-utils/src/geos/utils/algebraFunctions.py
+++ b/geos-utils/src/geos/utils/algebraFunctions.py
@@ -11,8 +11,7 @@
def getAttributeMatrixFromVector( attrArray: npt.NDArray[ np.float64 ], ) -> npt.NDArray[ np.float64 ]:
- r"""Get the matrix of attribute values from the vector.
-
+ """Get the matrix of attribute values from the vector.
Matrix to vector conversion is the following:
* if input vector size is 3:
@@ -24,7 +23,16 @@ def getAttributeMatrixFromVector( attrArray: npt.NDArray[ np.float64 ], ) -> npt
0 & 0 & v2
\end{bmatrix}
- * if input vector size is 6:
+ * if input vector size is 9:
+ .. math::
+
+ (v1, v2, v3, v4, v5, v6, v7, v8, v9) => \begin{bmatrix}
+ v1 & v6 & v5 \\
+ v9 & v2 & v4 \\
+ v8 & v7 & v3
+ \end{bmatrix}
+
+ * if input vector size is 6 (symmetrical tensor):
.. math::
(v1, v2, v3, v4, v5, v6) => \begin{bmatrix}
@@ -33,8 +41,8 @@ def getAttributeMatrixFromVector( attrArray: npt.NDArray[ np.float64 ], ) -> npt
v5 & v4 & v3
\end{bmatrix}
-
- .. WARNING:: Input vector must be of size 3 or 6.
+ .. Note::
+ In the case of 3 x 3 tensors, GEOS is currently only implemented for symmetrical cases.
Args:
attrArray (npt.NDArray[np.float64]): Vector of attribute values.
@@ -42,25 +50,42 @@ def getAttributeMatrixFromVector( attrArray: npt.NDArray[ np.float64 ], ) -> npt
Returns:
npt.NDArray[np.float64]: matrix of attribute values
+ Raises:
+ ValueError: The input vector must be of size 3, 9 or 6 (symmetrical case).
+
"""
- assert attrArray.size > 2, ( "Vectorial attribute must contains at least " + "3 components." )
+ if attrArray.size not in ( 3, 6, 9 ):
+ raise ValueError( "Vectorial attribute must contain 3, 6 or 9 components." )
+
# diagonal terms
matrix: npt.NDArray[ np.float64 ] = np.diagflat( attrArray[ :3 ] )
+
# shear stress components
if attrArray.size == 6:
- matrix[ 0, 1 ] = attrArray[ 5 ]
+ matrix[ 0, 1 ] = attrArray[ 5 ] # v1
matrix[ 1, 0 ] = attrArray[ 5 ]
- matrix[ 0, 2 ] = attrArray[ 4 ]
+ matrix[ 0, 2 ] = attrArray[ 4 ] # v5
matrix[ 2, 0 ] = attrArray[ 4 ]
- matrix[ 1, 2 ] = attrArray[ 3 ]
+ matrix[ 1, 2 ] = attrArray[ 3 ] # v4
matrix[ 2, 1 ] = attrArray[ 3 ]
+
+ elif attrArray.size == 9:
+ matrix[ 0, 1 ] = attrArray[ 5 ] # v1
+ matrix[ 1, 0 ] = attrArray[ 8 ] # v9
+
+ matrix[ 0, 2 ] = attrArray[ 4 ] # v5
+ matrix[ 2, 0 ] = attrArray[ 7 ] # v8
+
+ matrix[ 1, 2 ] = attrArray[ 3 ] # v4
+ matrix[ 2, 1 ] = attrArray[ 6 ] # v7
+
return matrix
def getAttributeVectorFromMatrix( attrMatrix: npt.NDArray[ np.float64 ], size: int ) -> npt.NDArray[ np.float64 ]:
- r"""Get the vector of attribute values from the matrix.
+ """Get the vector of attribute values from the matrix.
Matrix to vector conversion is the following:
@@ -74,7 +99,17 @@ def getAttributeVectorFromMatrix( attrMatrix: npt.NDArray[ np.float64 ], size: i
\end{bmatrix}
=> (M00, M11, M22)
- * otherwise:
+ * 3x3 Generic matrix:
+ .. math::
+
+ \begin{bmatrix}
+ M00 & M01 & M02 \\
+ M10 & M11 & M12 \\
+ M20 & M21 & M22
+ \end{matrix}
+ => (M00, M11, M22, M12, M02, M01, M21, M20, M10)
+
+ * Symmetric case
.. math::
\begin{bmatrix}
@@ -84,20 +119,57 @@ def getAttributeVectorFromMatrix( attrMatrix: npt.NDArray[ np.float64 ], size: i
\end{bmatrix}
=> (M00, M11, M22, M12, M02, M01)
+
+ .. Note::
+ In the case of 3 x 3 tensors, GEOS is currently only implemented for symmetrical cases.
+
Args:
attrMatrix (npt.NDArray[np.float64]): Matrix of attribute values.
- size (int): Size of the final vector.
+ size (int): Size of the final vector. Values accepted are 3, 9 or 6.
Returns:
npt.NDArray[np.float64]: vector of attribute values
+ Raises:
+ ValueError: The output vector size requested can only be 3, 9 or 6.
+
"""
attrArray: npt.NDArray[ np.float64 ] = np.full( size, np.nan )
- # diagonal terms
+ if size not in ( 3, 6, 9 ):
+ raise ValueError( "Requested output size can only be 3, 9 or 6 (symmetrical case)." )
+
+ # Diagonal terms
attrArray[ :3 ] = np.diag( attrMatrix )
+
# shear stress components
if attrArray.size == 6:
attrArray[ 3 ] = attrMatrix[ 1, 2 ]
attrArray[ 4 ] = attrMatrix[ 0, 2 ]
attrArray[ 5 ] = attrMatrix[ 0, 1 ]
+
+ elif attrArray.size == 9:
+ attrArray[ 3 ] = attrMatrix[ 1, 2 ]
+ attrArray[ 4 ] = attrMatrix[ 0, 2 ]
+ attrArray[ 5 ] = attrMatrix[ 0, 1 ]
+ attrArray[ 6 ] = attrMatrix[ 2, 1 ]
+ attrArray[ 7 ] = attrMatrix[ 2, 0 ]
+ attrArray[ 8 ] = attrMatrix[ 1, 0 ]
+
return attrArray
+
+
+def getLocalToXYZTransformMatrix( localBasis ) -> npt.NDArray[ np.float64 ]:
+ """Compute and return the transformation matrix to convert from local basis to XYZ basis.
+
+ Args:
+ localBasis (npt.NDArray[np.float64]): Local basis coordinates vectors in fonction of XYZ basis.
+
+ Returns:
+ npt.NDArray[ np.float64 ]: Local to XYZ transformation matrix.
+ """
+ if localBasis.shape != ( 3, 3 ):
+ raise ValueError( "Expected a 3x3 matrix." )
+
+ P: npt.NDArray[ np.float64 ] = np.transpose( localBasis )
+
+ return P
\ No newline at end of file
From 9c10aeabb44768bcabceabf0b03210b40618cb0d Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Tue, 21 Oct 2025 15:06:28 +0200
Subject: [PATCH 09/62] Refactor of normal and tangential vectors computation
---
.../src/geos/mesh/utils/genericHelpers.py | 171 ++++++++++++++----
.../src/geos_posp/filters/GeosBlockMerge.py | 69 +------
2 files changed, 147 insertions(+), 93 deletions(-)
diff --git a/geos-mesh/src/geos/mesh/utils/genericHelpers.py b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
index 57ee6b22d..cb7483c22 100644
--- a/geos-mesh/src/geos/mesh/utils/genericHelpers.py
+++ b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
@@ -3,14 +3,22 @@
# SPDX-FileContributor: Martin Lemay, Paloma Martinez
import numpy as np
import numpy.typing as npt
-from typing import Iterator, List, Sequence, Any, Union
-from vtkmodules.util.numpy_support import (
- numpy_to_vtk,
- vtk_to_numpy
+from typing import Iterator, List, Sequence, Any, Union, Tuple
+from vtkmodules.util.numpy_support import ( numpy_to_vtk, vtk_to_numpy )
+from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints, reference, vtkDataArray
+from vtkmodules.vtkCommonDataModel import (
+ vtkUnstructuredGrid,
+ vtkMultiBlockDataSet,
+ vtkPolyData,
+ vtkDataSet,
+ vtkDataObject,
+ vtkPlane,
+ vtkCellTypes,
+ vtkIncrementalOctreePointLocator,
)
-from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints, reference
-from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, vtkMultiBlockDataSet, vtkPolyData, vtkDataSet, vtkDataObject, vtkPlane, vtkCellTypes, vtkIncrementalOctreePointLocator
-from vtkmodules.vtkFiltersCore import vtk3DLinearGridPlaneCutter
+from vtkmodules.vtkFiltersCore import ( vtk3DLinearGridPlaneCutter, vtkPolyDataNormals, vtkPolyDataTangents )
+from vtkmodules.vtkFiltersTexture import vtkTextureMapToPlane
+
from geos.mesh.utils.multiblockHelpers import ( getBlockElementIndexesFlatten, getBlockFromFlatIndex )
from geos.utils.algebraFunctions import (
@@ -19,7 +27,6 @@
getLocalToXYZTransformMatrix,
)
-
__doc__ = """
Generic VTK utilities.
@@ -279,9 +286,9 @@ def createVertices( cellPtsCoord: list[ npt.NDArray[ np.float64 ] ],
return points, cellVertexMapAll
-def convertAttributeFromLocalToXYZForOneCell( vector, localBasisVectors ) -> npt.NDArray[ np.float64 ]:
- """
- Convert one cell attribute from local to XYZ basis.
+def convertAttributeFromLocalToXYZForOneCell(
+ vector: npt.NDArray[ np.float64 ], localBasisVectors: npt.NDArray[ np.float64 ] ) -> npt.NDArray[ np.float64 ]:
+ """Convert one cell attribute from local to XYZ basis.
.. Warning::
Vectors components are considered to be in GEOS output order such that \
@@ -303,48 +310,150 @@ def convertAttributeFromLocalToXYZForOneCell( vector, localBasisVectors ) -> npt
# Apply transformation
arrayXYZ: npt.NDArray[ np.float64 ] = transformMatrix @ matrix3x3 @ transformMatrix.T
- # Convert back to GEOS type attribute
- vectorXYZ: npt.NDArray[ np.float64 ] = getAttributeVectorFromMatrix( arrayXYZ, vector.size )
+ # Convert back to GEOS type attribute and return
+ return getAttributeVectorFromMatrix( arrayXYZ, vector.size )
- return vectorXYZ
+def getNormalVectors( surface: vtkPolyData ) -> npt.NDArray[ np.float64 ]:
+ """Return the normal vectors of a surface mesh.
+
+ Args:
+ surface (vtkPolyData): The input surface.
+
+ Raises:
+ ValueError: No normal attribute found in the mesh. Use the computeNormals function beforehand.
-def getNormalVectors( dataset, ) -> npt.NDArray[ np.float64 ]:
- normals: npt.NDArray[ np.float64 ] = vtk_to_numpy(
- dataset.GetCellData().GetNormals() ) # type: ignore[no-untyped-call]
- # TODO: if normals not defined, compute it on the fly
+ Returns:
+ npt.NDArray[ np.float64 ]: The normal vectors of the input surface.
+ """
+ normals: Union[ npt.NDArray[ np.float64 ],
+ None ] = vtk_to_numpy( surface.GetCellData().GetNormals() ) # type: ignore[no-untyped-call]
if normals is None:
- raise ValueError( "Normal attribute was not found." )
+ raise ValueError( "No normal attribute found in the mesh. Use the computeNormals function beforehand." )
return normals
-def getTangentsVectors( dataset, normals = None ) -> npt.NDArray[ np.float64 ]:
+def getTangentsVectors( surface: vtkPolyData ) -> Tuple[ npt.NDArray[ np.float64 ], npt.NDArray[ np.float64 ] ]:
+ """Return the tangential vectors of a surface.
+
+ Args:
+ surface (vtkPolyData): The input surface.
+
+ Raises:
+ ValueError: Tangent attribute not found in the mesh. Use computeTangents beforehand.
+ ValueError: Problem during the calculation of the second tangential vectors.
+
+ Returns:
+ Tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: The tangents vectors of the input surface.
+ """
# Get first tangential component
- tangents1: npt.NDArray[ np.float64 ] = vtk_to_numpy(
- dataset.GetCellData().GetTangents() ) # type: ignore[no-untyped-call]
+ tangents1: Union[ npt.NDArray[ np.float64 ],
+ None ] = vtk_to_numpy( surface.GetCellData().GetTangents() ) # type: ignore[no-untyped-call]
if tangents1 is None:
- raise ValueError( "Tangents attribute was not found." )
+ raise ValueError( "No tangential attribute found in the mesh. Use the computeTangents function beforehand." )
# Compute second tangential component
- if normals is None:
- normals = getNormalVectors( dataset )
+ normals: npt.NDArray[ np.float64 ] = getNormalVectors( surface )
- tangents2: npt.NDArray[ np.float64 ] = np.cross( normals, tangents1, axis=1 ).astype( np.float64 )
+ tangents2: Union[ npt.NDArray[ np.float64 ], None ] = np.cross( normals, tangents1, axis=1 ).astype( np.float64 )
if tangents2 is None:
raise ValueError( "Local basis third axis was not computed." )
return ( tangents1, tangents2 )
-def getLocalBasisVectors( dataset: vtkPolyData ) -> npt.NDArray[ np.float64 ]:
- """Return the local basis vectors for all cells of the input dataset.
+def getLocalBasisVectors( surface: vtkPolyData ) -> npt.NDArray[ np.float64 ]:
+ """Return the local basis vectors for all cells of the input surface.
Args:
- dataset(vtkPolydata): The input dataset
+ surface(vtkPolydata): The input surface.
+ Returns:
+ Tuple[npt.NDArray[np.float64], npt.NDArray[np.float64], npt.NDArray[np.float64]]: Array with normal, tangential 1 and tangential 2 vectors.
"""
- normals = getNormalVectors( dataset )
- tangents = getTangentsVectors( dataset, normals=normals )
+ try:
+ normals: npt.NDArray[ np.float64 ] = getNormalVectors( surface )
+ except ValueError:
+ surfaceWithNormals: vtkPolyData = computeNormals( surface )
+ normals = getNormalVectors( surfaceWithNormals )
+
+ try:
+ tangents: Tuple[ npt.NDArray[ np.float64 ], npt.NDArray[ np.float64 ] ] = getTangentsVectors( surface )
+ except ValueError:
+ surfaceWithTangents: vtkPolyData = computeTangents( surface )
+ tangents = getTangentsVectors( surfaceWithTangents )
return np.array( ( normals, *tangents ) )
+
+
+def computeNormals( surface: vtkPolyData ) -> vtkPolyData:
+ """Compute and set the normals of a given surface.
+
+ Args:
+ surface (vtkPolyData): The input surface.
+
+ Returns:
+ vtkPolyData: The surface with normal attribute.
+ """
+ normalFilter: vtkPolyDataNormals = vtkPolyDataNormals()
+ normalFilter.SetInputData( surface )
+ normalFilter.ComputeCellNormalsOn()
+ normalFilter.ComputePointNormalsOff()
+ normalFilter.Update()
+
+ return normalFilter.GetOutput()
+
+
+def computeTangents( triangulatedSurface: vtkPolyData ) -> vtkPolyData:
+ """Compute and set the tangents of a given surface.
+
+ .. Warning:: The computation of tangents requires a triangulated surface.
+
+ Args:
+ triangulatedSurface (vtkPolyData): The input surface. It should be triangulated beforehand.
+
+ Returns:
+ vtkPolyData: The surface with tangent attribute
+ """
+ # need to compute texture coordinates required for tangent calculation
+ surface1: vtkPolyData = computeSurfaceTextureCoordinates( triangulatedSurface )
+
+ # TODO: triangulate the surface before computation of the tangents if needed.
+
+ # compute tangents
+ tangentFilter: vtkPolyDataTangents = vtkPolyDataTangents()
+ tangentFilter.SetInputData( surface1 )
+ tangentFilter.ComputeCellTangentsOn()
+ tangentFilter.ComputePointTangentsOff()
+ tangentFilter.Update()
+ surfaceOut: vtkPolyData = tangentFilter.GetOutput()
+ assert surfaceOut is not None, "Tangent components calculation failed."
+
+ # copy tangents attributes into filter input surface because surface attributes
+ # are not transferred to the output (bug that should be corrected by Kitware)
+ array: vtkDataArray = surfaceOut.GetCellData().GetTangents()
+ assert array is not None, "Attribute Tangents is not in the mesh."
+
+ surface1.GetCellData().SetTangents( array )
+ surface1.GetCellData().Modified()
+ surface1.Modified()
+ return surface1
+
+
+def computeSurfaceTextureCoordinates( surface: vtkPolyData ) -> vtkPolyData:
+ """Generate the 2D texture coordinates required for tangent computation. The dataset points are mapped onto a plane generated automatically.
+
+ Args:
+ surface (vtkPolyData): The input surface.
+
+ Returns:
+ vtkPolyData: The input surface with generated texture map.
+ """
+ # Need to compute texture coordinates required for tangent calculation
+ textureFilter: vtkTextureMapToPlane = vtkTextureMapToPlane()
+ textureFilter.SetInputData( surface )
+ textureFilter.AutomaticPlaneGenerationOn()
+ textureFilter.Update()
+
+ return textureFilter.GetOutput()
diff --git a/geos-posp/src/geos_posp/filters/GeosBlockMerge.py b/geos-posp/src/geos_posp/filters/GeosBlockMerge.py
index 8d24b5934..154d8cac4 100644
--- a/geos-posp/src/geos_posp/filters/GeosBlockMerge.py
+++ b/geos-posp/src/geos_posp/filters/GeosBlockMerge.py
@@ -14,7 +14,6 @@
from typing_extensions import Self
from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase
from vtkmodules.vtkCommonCore import (
- vtkDataArray,
vtkInformation,
vtkInformationVector,
)
@@ -25,19 +24,18 @@
vtkPolyData,
vtkUnstructuredGrid,
)
-from vtkmodules.vtkFiltersCore import (
- vtkArrayRename,
- vtkPolyDataNormals,
- vtkPolyDataTangents,
-)
+from vtkmodules.vtkFiltersCore import vtkArrayRename
from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter
-from vtkmodules.vtkFiltersTexture import vtkTextureMapToPlane
from geos.mesh.utils.multiblockHelpers import getElementaryCompositeBlockIndexes
from geos.mesh.utils.arrayHelpers import getAttributeSet
from geos.mesh.utils.arrayModifiers import createConstantAttribute, fillAllPartialAttributes
from geos.mesh.utils.multiblockHelpers import extractBlock
from geos.mesh.utils.multiblockModifiers import mergeBlocks
+from geos.mesh.utils.genericHelpers import (
+ computeNormals,
+ computeTangents,
+)
__doc__ = """
GeosBlockMerge module is a vtk filter that allows to merge Geos ranks, rename
@@ -397,10 +395,10 @@ def convertFaultsToSurfaces( self: Self ) -> bool:
surface1: vtkPolyData = self.convertBlockToSurface( surface0 )
assert surface1 is not None, "Surface extraction from block failed."
# compute normals
- surface2: vtkPolyData = self.computeNormals( surface1 )
+ surface2: vtkPolyData = computeNormals( surface1 )
assert surface2 is not None, "Normal calculation failed."
# compute tangents
- surface3: vtkPolyData = self.computeTangents( surface2 )
+ surface3: vtkPolyData = computeTangents( surface2 )
assert surface3 is not None, "Tangent calculation failed."
# set surface to output multiBlockDataSet
@@ -437,56 +435,3 @@ def convertBlockToSurface( self: Self, block: vtkUnstructuredGrid ) -> vtkPolyDa
extractSurfaceFilter.Update()
output: vtkPolyData = extractSurfaceFilter.GetOutput()
return output
-
- def computeNormals( self: Self, surface: vtkPolyData ) -> vtkPolyData:
- """Compute normals of the given surface.
-
- Args:
- surface (vtkPolyData): surface to compute the normals
-
- Returns:
- vtkPolyData: surface with normal attribute
- """
- normalFilter: vtkPolyDataNormals = vtkPolyDataNormals()
- normalFilter.SetInputData( surface )
- normalFilter.ComputeCellNormalsOn()
- normalFilter.ComputePointNormalsOff()
- normalFilter.Update()
- output: vtkPolyData = normalFilter.GetOutput()
- return output
-
- def computeTangents( self: Self, surface: vtkPolyData ) -> vtkPolyData:
- """Compute tangents of the given surface.
-
- Args:
- surface (vtkPolyData): surface to compute the tangents
-
- Returns:
- vtkPolyData: surface with tangent attribute
- """
- # need to compute texture coordinates required for tangent calculation
- textureFilter: vtkTextureMapToPlane = vtkTextureMapToPlane()
- textureFilter.SetInputData( surface )
- textureFilter.AutomaticPlaneGenerationOn()
- textureFilter.Update()
- surface1: vtkPolyData = textureFilter.GetOutput()
- assert ( surface1 is not None ), "Texture calculation during Tangent calculation failed."
-
- # compute tangents
- tangentFilter: vtkPolyDataTangents = vtkPolyDataTangents()
- tangentFilter.SetInputData( surface1 )
- tangentFilter.ComputeCellTangentsOn()
- tangentFilter.ComputePointTangentsOff()
- tangentFilter.Update()
- surfaceOut: vtkPolyData = tangentFilter.GetOutput()
- assert surfaceOut is not None, "Tangent components calculation failed."
-
- # copy tangents attributes into filter input surface because surface attributes
- # are not transferred to the output (bug that should be corrected by Kitware)
- array: vtkDataArray = surfaceOut.GetCellData().GetTangents()
- assert array is not None, "Attribute Tangents is not in the mesh."
-
- surface1.GetCellData().SetTangents( array )
- surface1.GetCellData().Modified()
- surface1.Modified()
- return surface1
From 4643add850a84c2762a30b979334aacee0f9c2d0 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Wed, 29 Oct 2025 09:09:16 +0100
Subject: [PATCH 10/62] Replace and add tests for attribute to vector functions
---
.../src/geos/utils/GeosOutputsConstants.py | 2 +-
geos-utils/src/geos/utils/algebraFunctions.py | 84 ++++++++-----------
.../src/geos/utils/geometryFunctions.py | 4 +
geos-utils/tests/test_algebraFunctions.py | 73 ++++++++++++++++
geos-utils/tests/testsFunctionsGeosUtils.py | 26 ------
5 files changed, 112 insertions(+), 77 deletions(-)
create mode 100644 geos-utils/tests/test_algebraFunctions.py
delete mode 100644 geos-utils/tests/testsFunctionsGeosUtils.py
diff --git a/geos-utils/src/geos/utils/GeosOutputsConstants.py b/geos-utils/src/geos/utils/GeosOutputsConstants.py
index 1ad99fbfc..4b10574af 100644
--- a/geos-utils/src/geos/utils/GeosOutputsConstants.py
+++ b/geos-utils/src/geos/utils/GeosOutputsConstants.py
@@ -299,4 +299,4 @@ def getAttributeToTransferFromInitialTime() -> dict[ str, str ]:
PostProcessingOutputsEnum.YOUNG_MODULUS_INITIAL.attributeName,
PostProcessingOutputsEnum.POISSON_RATIO.attributeName:
PostProcessingOutputsEnum.POISSON_RATIO_INITIAL.attributeName,
- }
\ No newline at end of file
+ }
diff --git a/geos-utils/src/geos/utils/algebraFunctions.py b/geos-utils/src/geos/utils/algebraFunctions.py
index 0afb84cd9..876f88873 100644
--- a/geos-utils/src/geos/utils/algebraFunctions.py
+++ b/geos-utils/src/geos/utils/algebraFunctions.py
@@ -1,45 +1,46 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Martin Lemay
+# SPDX-FileContributor: Martin Lemay, Paloma Martinez
+# ruff: noqa: E402 # disable Module level import not at top of file
import numpy as np
import numpy.typing as npt
__doc__ = """
-GeosUtils module defines usefull methods to process GEOS outputs according to
-GEOS conventions.
+The algebraFunctions module of `geos-utils` package defines methods to switch from vector <> matrix and conversely, following GEOS outputs conventions.
"""
def getAttributeMatrixFromVector( attrArray: npt.NDArray[ np.float64 ], ) -> npt.NDArray[ np.float64 ]:
- """Get the matrix of attribute values from the vector.
+ r"""Get the matrix of attribute values from the vector.
+
Matrix to vector conversion is the following:
* if input vector size is 3:
.. math::
- (v1, v2, v3) => \begin{bmatrix}
+ (v1, v2, v3) => \\begin{bmatrix}
v0 & 0 & 0 \\
0 & v1 & 0 \\
0 & 0 & v2
- \end{bmatrix}
+ \\end{bmatrix}
* if input vector size is 9:
.. math::
- (v1, v2, v3, v4, v5, v6, v7, v8, v9) => \begin{bmatrix}
+ (v1, v2, v3, v4, v5, v6, v7, v8, v9) => \\begin{bmatrix}
v1 & v6 & v5 \\
v9 & v2 & v4 \\
v8 & v7 & v3
- \end{bmatrix}
+ \\end{bmatrix}
* if input vector size is 6 (symmetrical tensor):
.. math::
- (v1, v2, v3, v4, v5, v6) => \begin{bmatrix}
+ (v1, v2, v3, v4, v5, v6) => \\begin{bmatrix}
v1 & v6 & v5 \\
v6 & v2 & v4 \\
v5 & v4 & v3
- \end{bmatrix}
+ \\end{bmatrix}
.. Note::
In the case of 3 x 3 tensors, GEOS is currently only implemented for symmetrical cases.
@@ -47,12 +48,11 @@ def getAttributeMatrixFromVector( attrArray: npt.NDArray[ np.float64 ], ) -> npt
Args:
attrArray (npt.NDArray[np.float64]): Vector of attribute values.
- Returns:
- npt.NDArray[np.float64]: matrix of attribute values
-
Raises:
ValueError: The input vector must be of size 3, 9 or 6 (symmetrical case).
+ Returns:
+ npt.NDArray[np.float64]: The matrix of attribute values.
"""
if attrArray.size not in ( 3, 6, 9 ):
raise ValueError( "Vectorial attribute must contain 3, 6 or 9 components." )
@@ -62,64 +62,63 @@ def getAttributeMatrixFromVector( attrArray: npt.NDArray[ np.float64 ], ) -> npt
# shear stress components
if attrArray.size == 6:
- matrix[ 0, 1 ] = attrArray[ 5 ] # v1
+ matrix[ 0, 1 ] = attrArray[ 5 ] # v1
matrix[ 1, 0 ] = attrArray[ 5 ]
- matrix[ 0, 2 ] = attrArray[ 4 ] # v5
+ matrix[ 0, 2 ] = attrArray[ 4 ] # v5
matrix[ 2, 0 ] = attrArray[ 4 ]
- matrix[ 1, 2 ] = attrArray[ 3 ] # v4
+ matrix[ 1, 2 ] = attrArray[ 3 ] # v4
matrix[ 2, 1 ] = attrArray[ 3 ]
elif attrArray.size == 9:
- matrix[ 0, 1 ] = attrArray[ 5 ] # v1
- matrix[ 1, 0 ] = attrArray[ 8 ] # v9
+ matrix[ 0, 1 ] = attrArray[ 5 ] # v1
+ matrix[ 1, 0 ] = attrArray[ 8 ] # v9
- matrix[ 0, 2 ] = attrArray[ 4 ] # v5
- matrix[ 2, 0 ] = attrArray[ 7 ] # v8
+ matrix[ 0, 2 ] = attrArray[ 4 ] # v5
+ matrix[ 2, 0 ] = attrArray[ 7 ] # v8
- matrix[ 1, 2 ] = attrArray[ 3 ] # v4
- matrix[ 2, 1 ] = attrArray[ 6 ] # v7
+ matrix[ 1, 2 ] = attrArray[ 3 ] # v4
+ matrix[ 2, 1 ] = attrArray[ 6 ] # v7
return matrix
def getAttributeVectorFromMatrix( attrMatrix: npt.NDArray[ np.float64 ], size: int ) -> npt.NDArray[ np.float64 ]:
- """Get the vector of attribute values from the matrix.
+ r"""Get the vector of attribute values from the matrix.
Matrix to vector conversion is the following:
* 3x3 diagonal matrix:
.. math::
- \begin{bmatrix}
+ \\begin{bmatrix}
M00 & 0 & 0 \\
0 & M11 & 0 \\
0 & 0 & M22
- \end{bmatrix}
+ \\end{bmatrix}
=> (M00, M11, M22)
* 3x3 Generic matrix:
.. math::
- \begin{bmatrix}
+ \\begin{bmatrix}
M00 & M01 & M02 \\
M10 & M11 & M12 \\
M20 & M21 & M22
- \end{matrix}
+ \\end{matrix}
=> (M00, M11, M22, M12, M02, M01, M21, M20, M10)
* Symmetric case
.. math::
- \begin{bmatrix}
+ \\begin{bmatrix}
M00 & M01 & M02 \\
M01 & M11 & M12 \\
M02 & M12 & M22
- \end{bmatrix}
+ \\end{bmatrix}
=> (M00, M11, M22, M12, M02, M01)
-
.. Note::
In the case of 3 x 3 tensors, GEOS is currently only implemented for symmetrical cases.
@@ -127,16 +126,18 @@ def getAttributeVectorFromMatrix( attrMatrix: npt.NDArray[ np.float64 ], size: i
attrMatrix (npt.NDArray[np.float64]): Matrix of attribute values.
size (int): Size of the final vector. Values accepted are 3, 9 or 6.
- Returns:
- npt.NDArray[np.float64]: vector of attribute values
-
Raises:
ValueError: The output vector size requested can only be 3, 9 or 6.
+ ValueError: Input matrix shape must be (3,3).
+ Returns:
+ npt.NDArray[np.float64]: Vector of attribute values.
"""
attrArray: npt.NDArray[ np.float64 ] = np.full( size, np.nan )
if size not in ( 3, 6, 9 ):
raise ValueError( "Requested output size can only be 3, 9 or 6 (symmetrical case)." )
+ if attrMatrix.shape != ( 3, 3 ):
+ raise ValueError( "Input matrix shape must be (3,3)." )
# Diagonal terms
attrArray[ :3 ] = np.diag( attrMatrix )
@@ -156,20 +157,3 @@ def getAttributeVectorFromMatrix( attrMatrix: npt.NDArray[ np.float64 ], size: i
attrArray[ 8 ] = attrMatrix[ 1, 0 ]
return attrArray
-
-
-def getLocalToXYZTransformMatrix( localBasis ) -> npt.NDArray[ np.float64 ]:
- """Compute and return the transformation matrix to convert from local basis to XYZ basis.
-
- Args:
- localBasis (npt.NDArray[np.float64]): Local basis coordinates vectors in fonction of XYZ basis.
-
- Returns:
- npt.NDArray[ np.float64 ]: Local to XYZ transformation matrix.
- """
- if localBasis.shape != ( 3, 3 ):
- raise ValueError( "Expected a 3x3 matrix." )
-
- P: npt.NDArray[ np.float64 ] = np.transpose( localBasis )
-
- return P
\ No newline at end of file
diff --git a/geos-utils/src/geos/utils/geometryFunctions.py b/geos-utils/src/geos/utils/geometryFunctions.py
index 6898b5e55..eb1843758 100644
--- a/geos-utils/src/geos/utils/geometryFunctions.py
+++ b/geos-utils/src/geos/utils/geometryFunctions.py
@@ -9,6 +9,10 @@
__doc__ = """Functions to permform geometry calculations."""
+CANONICAL_BASIS_3D: tuple[ npt.NDArray[ np.float64 ], npt.NDArray[ np.float64 ],
+ npt.NDArray[ np.float64 ] ] = ( np.array( [ 1.0, 0.0, 0.0 ] ), np.array( [ 0.0, 1.0, 0.0 ] ),
+ np.array( [ 0.0, 0.0, 1.0 ] ) )
+
def getChangeOfBasisMatrix(
basisFrom: tuple[ npt.NDArray[ np.floating[ Any ] ], npt.NDArray[ np.floating[ Any ] ],
diff --git a/geos-utils/tests/test_algebraFunctions.py b/geos-utils/tests/test_algebraFunctions.py
new file mode 100644
index 000000000..7d3effbaf
--- /dev/null
+++ b/geos-utils/tests/test_algebraFunctions.py
@@ -0,0 +1,73 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: Paloma Martinez
+
+import numpy as np
+import numpy.typing as npt
+from unittest import TestCase
+from typing.extensions import Self
+
+from geos.utils.algebraFunctions import (
+ getAttributeMatrixFromVector,
+ getAttributeVectorFromMatrix,
+)
+
+
+class TestAttributeMatrixFromVector( TestCase ):
+ """Tests for conversion from matrix to vector with GEOS convention."""
+
+ def test_wrongInputVectorSize( self: Self ) -> None:
+ """Test failure on incorrect input vector size."""
+ emptyVector: npt.NDArray[ np.float64 ] = np.array( [] )
+ with self.assertRaises( ValueError ):
+ getAttributeMatrixFromVector( emptyVector )
+
+ def test_vector3size( self: Self ) -> None:
+ """Test for an input vector size of 3."""
+ vector3 = np.arange( 1, 4 )
+ expectedMatrix: npt.NDArray[ np.float64 ] = np.array( [ [ 1, 0, 0 ], [ 0, 2, 0 ], [ 0, 0, 3 ] ] )
+
+ self.assertTrue( np.array_equal( expectedMatrix, getAttributeMatrixFromVector( vector3 ) ) )
+
+ def test_vector6( self: Self ) -> None:
+ """Test for an input vector size of 6."""
+ vector6 = np.arange( 1, 7 )
+ expectedMatrix = np.array( [ [ 1, 6, 5 ], [ 6, 2, 4 ], [ 5, 4, 3 ] ] )
+
+ self.assertTrue( np.array_equal( expectedMatrix, getAttributeMatrixFromVector( vector6 ) ) )
+
+ def test_vector9( self: Self ) -> None:
+ """Test for an input vector size of 9."""
+ vector9 = np.arange( 1, 10 )
+ expectedMatrix = np.array( [ [ 1, 6, 5 ], [ 9, 2, 4 ], [ 8, 7, 3 ] ] )
+
+ self.assertTrue( np.array_equal( expectedMatrix, getAttributeMatrixFromVector( vector9 ) ) )
+
+
+class TestAttributeVectorFromMatrix( TestCase ):
+ """Tests for conversion from vector to matrix with GEOS convention."""
+
+ def setUp( self: Self ) -> None:
+ """Set up parameters."""
+ self.rdMatrix = np.arange( 1, 10 ).reshape( 3, 3 )
+ self.expected: npt.NDArray[ np.float64 ] = np.array( [ 1, 5, 9, 6, 3, 2, 8, 7, 4 ] )
+
+ def test_wrongInputMatrixShape( self ) -> None:
+ """Test failure on empty input matrix."""
+ emptyMatrix = np.array( [] )
+ with self.assertRaises( ValueError ):
+ getAttributeVectorFromMatrix( emptyMatrix, 3 )
+
+ def test_wrongOutputSize( self: Self ) -> None:
+ """Test failure on incorrect output size requested."""
+ size = 4
+ with self.assertRaises( ValueError ):
+ getAttributeVectorFromMatrix( self.rdMatrix, size )
+
+ def test_vecOutput( self: Self ) -> None:
+ """Test correct output for requested size."""
+ listSize = ( 3, 6, 9 )
+ for size in listSize:
+ with self.subTest( size ):
+ expectedVector = np.array( self.expected[ :size ] )
+ self.assertTrue( np.array_equal( expectedVector, getAttributeVectorFromMatrix( self.rdMatrix, size ) ) )
diff --git a/geos-utils/tests/testsFunctionsGeosUtils.py b/geos-utils/tests/testsFunctionsGeosUtils.py
deleted file mode 100644
index 673553f82..000000000
--- a/geos-utils/tests/testsFunctionsGeosUtils.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0
-# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Alexandre Benedicto, Martin Lemay
-# ruff: noqa: E402 # disable Module level import not at top of file
-import unittest
-
-import geos.utils.algebraFunctions as fcts
-import numpy as np
-import numpy.typing as npt
-from typing_extensions import Self
-
-matrix: npt.NDArray[ np.float64 ] = np.array( [ [ 11, 21, 31 ], [ 21, 22, 23 ], [ 31, 23, 33 ] ] )
-vector: npt.NDArray[ np.float64 ] = np.array( [ 11, 22, 33, 23, 31, 21 ] )
-
-
-class TestsFunctionsalgebraFunctions( unittest.TestCase ):
-
- def test_getAttributeMatrixFromVector( self: Self ) -> None:
- """Test conversion from Matrix to Vector for Geos stress."""
- obtained: npt.NDArray[ np.float64 ] = fcts.getAttributeMatrixFromVector( vector )
- self.assertTrue( np.all( np.equal( obtained, matrix ) ) )
-
- def test_getAttributeVectorFromMatrix( self: Self ) -> None:
- """Test conversion from Vector to Matrix for Geos stress."""
- obtained: npt.NDArray[ np.float64 ] = fcts.getAttributeVectorFromMatrix( matrix, 6 )
- self.assertTrue( np.all( np.equal( obtained, vector ) ) )
From 64494c8197ccd4d129d14dd7373d638921a5330c Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Wed, 29 Oct 2025 09:17:40 +0100
Subject: [PATCH 11/62] Add tests and better error handling
---
.../src/geos/mesh/utils/genericHelpers.py | 153 +++++++--
.../test_genericHelpers_normalAndTangents.py | 302 ++++++++++++++++++
2 files changed, 423 insertions(+), 32 deletions(-)
create mode 100644 geos-mesh/tests/test_genericHelpers_normalAndTangents.py
diff --git a/geos-mesh/src/geos/mesh/utils/genericHelpers.py b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
index 9d9a61fa2..5297c960e 100644
--- a/geos-mesh/src/geos/mesh/utils/genericHelpers.py
+++ b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
@@ -2,10 +2,11 @@
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Martin Lemay, Paloma Martinez
import numpy as np
+import logging
import numpy.typing as npt
from typing import Iterator, List, Sequence, Any, Union, Tuple
from vtkmodules.util.numpy_support import ( numpy_to_vtk, vtk_to_numpy )
-from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints, reference, vtkDataArray
+from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints, reference, vtkDataArray, vtkLogger
from vtkmodules.vtkCommonDataModel import (
vtkUnstructuredGrid,
vtkMultiBlockDataSet,
@@ -24,8 +25,10 @@
from geos.utils.algebraFunctions import (
getAttributeMatrixFromVector,
getAttributeVectorFromMatrix,
- getLocalToXYZTransformMatrix,
)
+from geos.utils.geometryFunctions import ( getChangeOfBasisMatrix, CANONICAL_BASIS_3D )
+from geos.utils.Logger import ( getLogger, Logger, VTKCaptureLog, RegexExceptionFilter )
+from geos.utils.Errors import VTKError
__doc__ = """
Generic VTK utilities.
@@ -287,7 +290,9 @@ def createVertices( cellPtsCoord: list[ npt.NDArray[ np.float64 ] ],
def convertAttributeFromLocalToXYZForOneCell(
- vector: npt.NDArray[ np.float64 ], localBasisVectors: npt.NDArray[ np.float64 ] ) -> npt.NDArray[ np.float64 ]:
+ vector: npt.NDArray[ np.float64 ], localBasisVectors: tuple[ npt.NDArray[ np.float64 ], npt.NDArray[ np.float64 ],
+ npt.NDArray[ np.float64 ] ]
+) -> npt.NDArray[ np.float64 ]:
"""Convert one cell attribute from local to XYZ basis.
.. Warning::
@@ -296,7 +301,7 @@ def convertAttributeFromLocalToXYZForOneCell(
Args:
vector (npt.NDArray[np.float64]): The attribute expressed in local basis. The size of the vector must be 3, 9 or 6 (symmetrical case)
- localBasisVectors (np.NDArray[np.float64]): The local basis vectors expressed in the XYZ basis.
+ localBasisVectors (tuple[ npt.NDArray[np.float64], npt.NDArray[np.float64], npt.NDArray[np.float64]]): The local basis vectors expressed in the XYZ basis.
Returns:
vectorXYZ (npt.NDArray[np.float64]): The attribute expressed in XYZ basis.
@@ -305,7 +310,7 @@ def convertAttributeFromLocalToXYZForOneCell(
matrix3x3: npt.NDArray[ np.float64 ] = getAttributeMatrixFromVector( vector )
# Get transform matrix
- transformMatrix: npt.NDArray[ np.float64 ] = getLocalToXYZTransformMatrix( localBasisVectors )
+ transformMatrix: npt.NDArray[ np.float64 ] = getChangeOfBasisMatrix( localBasisVectors, CANONICAL_BASIS_3D )
# Apply transformation
arrayXYZ: npt.NDArray[ np.float64 ] = transformMatrix @ matrix3x3 @ transformMatrix.T
@@ -327,11 +332,11 @@ def getNormalVectors( surface: vtkPolyData ) -> npt.NDArray[ np.float64 ]:
npt.NDArray[ np.float64 ]: The normal vectors of the input surface.
"""
normals: Union[ npt.NDArray[ np.float64 ],
- None ] = vtk_to_numpy( surface.GetCellData().GetNormals() ) # type: ignore[no-untyped-call]
+ None ] = surface.GetCellData().GetNormals() # type: ignore[no-untyped-call]
if normals is None:
raise ValueError( "No normal attribute found in the mesh. Use the computeNormals function beforehand." )
- return normals
+ return vtk_to_numpy( normals )
def getTangentsVectors( surface: vtkPolyData ) -> Tuple[ npt.NDArray[ np.float64 ], npt.NDArray[ np.float64 ] ]:
@@ -349,9 +354,11 @@ def getTangentsVectors( surface: vtkPolyData ) -> Tuple[ npt.NDArray[ np.float64
"""
# Get first tangential component
tangents1: Union[ npt.NDArray[ np.float64 ],
- None ] = vtk_to_numpy( surface.GetCellData().GetTangents() ) # type: ignore[no-untyped-call]
+ None ] = surface.GetCellData().GetTangents() # type: ignore[no-untyped-call]
if tangents1 is None:
raise ValueError( "No tangential attribute found in the mesh. Use the computeTangents function beforehand." )
+ else:
+ tangents1 = vtk_to_numpy( tangents1 )
# Compute second tangential component
normals: npt.NDArray[ np.float64 ] = getNormalVectors( surface )
@@ -374,44 +381,82 @@ def getLocalBasisVectors( surface: vtkPolyData ) -> npt.NDArray[ np.float64 ]:
"""
try:
normals: npt.NDArray[ np.float64 ] = getNormalVectors( surface )
+ surfaceWithNormals: vtkPolyData = surface
+ # ValueError raised if no normals found in the mesh
except ValueError:
- surfaceWithNormals: vtkPolyData = computeNormals( surface )
+ # In that case, the normals are computed.
+ surfaceWithNormals = computeNormals( surface )
normals = getNormalVectors( surfaceWithNormals )
+ # Tangents require normals to be present in the mesh
try:
- tangents: Tuple[ npt.NDArray[ np.float64 ], npt.NDArray[ np.float64 ] ] = getTangentsVectors( surface )
+ tangents: Tuple[ npt.NDArray[ np.float64 ],
+ npt.NDArray[ np.float64 ] ] = getTangentsVectors( surfaceWithNormals )
+ # If no tangents is present in the mesh, they are computed on that surface
except ValueError:
- surfaceWithTangents: vtkPolyData = computeTangents( surface )
+ surfaceWithTangents: vtkPolyData = computeTangents( surfaceWithNormals )
tangents = getTangentsVectors( surfaceWithTangents )
return np.array( ( normals, *tangents ) )
-def computeNormals( surface: vtkPolyData ) -> vtkPolyData:
+def computeNormals(
+ surface: vtkPolyData,
+ logger: Union[ Logger, None ] = None,
+) -> vtkPolyData:
"""Compute and set the normals of a given surface.
Args:
surface (vtkPolyData): The input surface.
+ logger (Union[Logger, None], optional): A logger to manage the output messages.
+ Defaults to None, an internal logger is used.
Returns:
vtkPolyData: The surface with normal attribute.
"""
- normalFilter: vtkPolyDataNormals = vtkPolyDataNormals()
- normalFilter.SetInputData( surface )
- normalFilter.ComputeCellNormalsOn()
- normalFilter.ComputePointNormalsOff()
- normalFilter.Update()
+ if logger is None:
+ logger = getLogger( "computeSurfaceNormals" )
+ # Creation of a child logger to deal with VTKErrors without polluting parent logger
+ vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger" )
+ vtkErrorLogger.propagate = False
+
+ vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR )
+
+ vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error
+ vtkErrorLogger.setLevel( logging.DEBUG )
+
+ with VTKCaptureLog() as capturedLog:
+ normalFilter: vtkPolyDataNormals = vtkPolyDataNormals()
+ normalFilter.SetInputData( surface )
+ normalFilter.ComputeCellNormalsOn()
+ normalFilter.ComputePointNormalsOff()
+ normalFilter.Update()
+
+ capturedLog.seek( 0 )
+ captured = capturedLog.read().decode()
+
+ vtkErrorLogger.debug( captured.strip() )
- return normalFilter.GetOutput()
+ outputSurface = normalFilter.GetOutput()
+ if outputSurface.GetCellData().GetNormals() is None:
+ raise VTKError( "Something went wrong with VTK calculation of the normals." )
-def computeTangents( triangulatedSurface: vtkPolyData ) -> vtkPolyData:
+ return outputSurface
+
+
+def computeTangents(
+ triangulatedSurface: vtkPolyData,
+ logger: Union[ Logger, None ] = None,
+) -> vtkPolyData:
"""Compute and set the tangents of a given surface.
.. Warning:: The computation of tangents requires a triangulated surface.
Args:
triangulatedSurface (vtkPolyData): The input surface. It should be triangulated beforehand.
+ logger (Union[Logger, None], optional): A logger to manage the output messages.
+ Defaults to None, an internal logger is used.
Returns:
vtkPolyData: The surface with tangent attribute
@@ -422,18 +467,39 @@ def computeTangents( triangulatedSurface: vtkPolyData ) -> vtkPolyData:
# TODO: triangulate the surface before computation of the tangents if needed.
# compute tangents
- tangentFilter: vtkPolyDataTangents = vtkPolyDataTangents()
- tangentFilter.SetInputData( surface1 )
- tangentFilter.ComputeCellTangentsOn()
- tangentFilter.ComputePointTangentsOff()
- tangentFilter.Update()
- surfaceOut: vtkPolyData = tangentFilter.GetOutput()
- assert surfaceOut is not None, "Tangent components calculation failed."
+ if logger is None:
+ logger = getLogger( "computeSurfaceTangents" )
+ # Creation of a child logger to deal with VTKErrors without polluting parent logger
+ vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger" )
+ vtkErrorLogger.propagate = False
+
+ vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR )
+
+ vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error
+ vtkErrorLogger.setLevel( logging.DEBUG )
+
+ with VTKCaptureLog() as capturedLog:
+
+ tangentFilter: vtkPolyDataTangents = vtkPolyDataTangents()
+ tangentFilter.SetInputData( surface1 )
+ tangentFilter.ComputeCellTangentsOn()
+ tangentFilter.ComputePointTangentsOff()
+ tangentFilter.Update()
+ surfaceOut: vtkPolyData = tangentFilter.GetOutput()
+
+ capturedLog.seek( 0 )
+ captured = capturedLog.read().decode()
+
+ vtkErrorLogger.debug( captured.strip() )
+
+ if surfaceOut is None:
+ raise VTKError( "Something went wrong in VTK calculation." )
# copy tangents attributes into filter input surface because surface attributes
# are not transferred to the output (bug that should be corrected by Kitware)
array: vtkDataArray = surfaceOut.GetCellData().GetTangents()
- assert array is not None, "Attribute Tangents is not in the mesh."
+ if array is None:
+ raise VTKError( "Attribute Tangents is not in the mesh." )
surface1.GetCellData().SetTangents( array )
surface1.GetCellData().Modified()
@@ -441,19 +507,42 @@ def computeTangents( triangulatedSurface: vtkPolyData ) -> vtkPolyData:
return surface1
-def computeSurfaceTextureCoordinates( surface: vtkPolyData ) -> vtkPolyData:
+def computeSurfaceTextureCoordinates(
+ surface: vtkPolyData,
+ logger: Union[ Logger, None ] = None,
+) -> vtkPolyData:
"""Generate the 2D texture coordinates required for tangent computation. The dataset points are mapped onto a plane generated automatically.
Args:
surface (vtkPolyData): The input surface.
+ logger (Union[Logger, None], optional): A logger to manage the output messages.
+ Defaults to None, an internal logger is used.
Returns:
vtkPolyData: The input surface with generated texture map.
"""
# Need to compute texture coordinates required for tangent calculation
- textureFilter: vtkTextureMapToPlane = vtkTextureMapToPlane()
- textureFilter.SetInputData( surface )
- textureFilter.AutomaticPlaneGenerationOn()
- textureFilter.Update()
+ if logger is None:
+ logger = getLogger( "computeSurfaceTextureCoordinates" )
+ # Creation of a child logger to deal with VTKErrors without polluting parent logger
+ vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger" )
+ vtkErrorLogger.propagate = False
+
+ vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR )
+
+ vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error
+ vtkErrorLogger.setLevel( logging.DEBUG )
+
+ with VTKCaptureLog() as capturedLog:
+
+ textureFilter: vtkTextureMapToPlane = vtkTextureMapToPlane()
+ textureFilter.SetInputData( surface )
+ textureFilter.AutomaticPlaneGenerationOn()
+ textureFilter.Update()
+
+ capturedLog.seek( 0 )
+ captured = capturedLog.read().decode()
+
+ vtkErrorLogger.debug( captured.strip() )
return textureFilter.GetOutput()
diff --git a/geos-mesh/tests/test_genericHelpers_normalAndTangents.py b/geos-mesh/tests/test_genericHelpers_normalAndTangents.py
new file mode 100644
index 000000000..22acccc4a
--- /dev/null
+++ b/geos-mesh/tests/test_genericHelpers_normalAndTangents.py
@@ -0,0 +1,302 @@
+# SPDX-FileContributor: Paloma Martinez
+# SPDX-License-Identifier: Apache 2.0
+# ruff: noqa: E402 # disable Module level import not at top of file
+import pytest
+from enum import IntEnum
+from typing_extensions import Self, Iterator
+import numpy.typing as npt
+import numpy as np
+from dataclasses import dataclass, field
+
+from vtkmodules.vtkCommonDataModel import ( vtkPolyData, vtkTriangle, vtkCellArray )
+from vtkmodules.vtkCommonCore import vtkPoints
+from vtkmodules.util.numpy_support import vtk_to_numpy
+
+from geos.mesh.utils.genericHelpers import ( computeSurfaceTextureCoordinates, computeTangents, computeNormals,
+ getLocalBasisVectors, getTangentsVectors, getNormalVectors,
+ convertAttributeFromLocalToXYZForOneCell )
+
+from geos.utils.Errors import VTKError
+
+# yapf: disable
+pointsCoordsAll: list[ list[ list[ float ] ] ] = [
+ [ [ 0., 0., 0. ], [ 0., 1., 0. ], [ 0., 2., 0. ], [ 0., 2., 1. ], [ 0., 1., 1. ], [ 0., 0., 1. ], ],
+ [ [ 0., 0., 0. ], [ 0., 1., 0. ], [ 0., 2., 0. ], [ 0., 2., 1. ], [ 1., 1., 1.5 ], [ 0., 0., 1. ], ],
+]
+trianglesAll: list[ list[ tuple[ int, int, int ] ] ] = [
+ [ ( 0, 1, 5 ), ( 1, 4, 5 ), ( 1, 2, 3 ), ( 1, 4, 3 ) ],
+ [ ( 0, 1, 5 ), ( 1, 4, 5 ), ( 1, 2, 3 ), ( 1, 4, 3 ) ],
+]
+expectedNormalsAll: list[ npt.NDArray[ np.float64 ] ] = [
+ np.array( [ [ 1., 0., 0. ], [ 1., 0., 0. ], [ 1., 0., 0. ], [ 1., 0., 0. ], ] ),
+ np.array( [ [ 1., 0., 0. ], [ 0.7276069, -0.48507124, -0.48507124 ], [ 1., 0., 0. ],
+ [ 0.7276069, 0.48507124, -0.48507124 ] ] ),
+]
+expectedTangentsAll: list[ npt.NDArray[ np.float64 ] ] = [
+ np.array( [ [ [ 0., 2., 0. ], [ -0., 2., 0. ], [ 0., 2., 0. ], [ 0., 2., 0. ] ],
+ [ [ 0., 0., 2. ], [ 0., -0., 2. ], [ 0., 0., 2. ], [ 0., 0., 2. ],
+ ] ] ),
+ np.array( [ [ [ 0., 2., 0. ], [ 0.8301887, 2., -0.754717 ], [ 0., 2., 0. ], [ -0.8301887, 2., 0.754717 ] ],
+ [ [ 0., 0., 2. ], [ 1.33623397, 0.14643663, 1.85791445 ], [ 0., 0., 2. ],
+ [ 1.33623397, -0.14643663, 1.85791445 ] ] ] ),
+]
+expectedTexturedCoordsAll: list[ npt.NDArray[ np.float64 ] ] = [
+ np.array( [ [ 0., 0. ], [ 0.5, 0. ], [ 1., 0. ], [ 1., 1. ], [ 0.5, 1. ], [ 0., 1. ],
+ ] ),
+ np.array( [ [ [ 0., 0. ], [ 0.5, 0. ], [ 1., 0. ], [ 1., 0.41509435 ], [ 0.5, 1. ], [ 0., 0.41509435 ] ],
+ ] ),
+]
+# Same attribute for all cells, test for vector size 3, 6, 9
+attributes: list[ npt.NDArray[ np.float64 ] ] = [ np.arange( 1., 4., dtype=np.float64 ), np.arange( 1., 7., dtype=np.float64 ), np.arange( 1., 10., dtype=np.float64 )
+]
+expectedAttributesXYZ: list[ list[ npt.NDArray[ np.float64 ] ] ] = [
+ [
+ np.array( [ [ 1., 8., 12. ], [ 1., 8., 12. ], [ 1., 8., 12. ], [ 1., 8., 12. ],
+ ] ),
+ np.array( [ [ 1., 8., 12., 16., 10., 12. ], [ 1., 8., 12., 16., 10., 12. ], [ 1., 8., 12., 16., 10., 12. ],
+ [ 1., 8., 12., 16., 10., 12. ] ] ),
+ np.array( [ [ 1., 8., 12., 16., 10., 12., 28., 16., 18. ], [ 1., 8., 12., 16., 10., 12., 28., 16., 18. ],
+ [ 1., 8., 12., 16., 10., 12., 28., 16., 18. ], [ 1., 8., 12., 16., 10., 12., 28., 16., 18. ],
+ [ 1., 8., 12., 16., 10., 12., 28., 16., 18. ], [ 1., 8., 12., 16., 10., 12., 28., 16., 18. ] ] )
+ ], # case 1
+ [
+ np.array( [ [ 1., 8., 12. ], [ 7.26440201, 8.29962517, 11.73002787 ], [ 1., 8., 12. ],
+ [ 7.26440201, 8.29962517, 11.73002787 ] ] ),
+ np.array( [ [ 1., 8., 12., 16., 10., 12. ],
+ [ 33.11015535, -1.70942051, -4.10667954, 3.96829788, 5.78481908, 18.33796323 ],
+ [ 1., 8., 12., 16., 10., 12. ],
+ [ 0.86370966, 16.88802688, 9.54231792, 17.62557587, 12.93534579, 16.64449814 ] ] ),
+ np.array( [ [ 1., 8., 12., 16., 10., 12., 28., 16., 18. ],
+ [ 41.16704654, -3.95432477, -9.91866643, 0.51321919, -0.39322437, 23.20275907, 13.51039649,
+ 12.82015999, 23.3879596
+ ], [ 1., 8., 12., 16., 10., 12., 28., 16., 18. ],
+ [ -1.35966323, 18.70673795, 9.9469796, 14.59669037, 15.22437721, 25.39830602, 32.57499969,
+ 14.01099303, 21.0552047
+ ] ] )
+ ], # case 2
+]
+# yapf: enable
+
+
+class Options( IntEnum ):
+ """Enum corresponding to data required for tests.
+
+ - RAW : bare mesh
+ - NORMALS : only normals are calculated
+ - NORMALS_TEXTURED : normals and textured coordinates are computed
+ - TANGENTS : normals and tangents are present in the mesh
+ """
+ RAW = 0
+ NORMALS = 1
+ NORMALS_TEXTURED = 2
+ TANGENTS = 3
+
+
+@dataclass
+class TriangulatedSurfaceTestCase:
+ """Test case."""
+ pointsCoords: list[ list[ float ] ]
+ triangles: list[ tuple[ int, int, int ] ]
+ attribute: list[ npt.NDArray ]
+ expectedNormals: npt.NDArray
+ expectedTangents: npt.NDArray
+ expectedTexturedCoords: npt.NDArray
+ expectedAttributeInXYZ: list[ npt.NDArray ]
+ options: Options
+ mesh: vtkPolyData = field( init=False )
+
+ def __post_init__( self: Self ) -> None:
+ """Generate the mesh."""
+ points: vtkPoints = vtkPoints()
+ for coord in self.pointsCoords:
+ points.InsertNextPoint( coord )
+
+ triangles: vtkCellArray = vtkCellArray()
+ for t in self.triangles:
+ triangle = vtkTriangle()
+ triangle.GetPointIds().SetId( 0, t[ 0 ] )
+ triangle.GetPointIds().SetId( 1, t[ 1 ] )
+ triangle.GetPointIds().SetId( 2, t[ 2 ] )
+ triangles.InsertNextCell( triangle )
+
+ polydata: vtkPolyData = vtkPolyData()
+ polydata.SetPoints( points )
+ polydata.SetPolys( triangles )
+
+ # Compute additional properties depending on the tests options
+ if self.options == Options.NORMALS:
+ self.mesh = computeNormals( polydata )
+
+ elif self.options == Options.NORMALS_TEXTURED:
+ mesh: vtkPolyData = computeSurfaceTextureCoordinates( polydata )
+ mesh2: vtkPolyData = computeNormals( mesh )
+ self.mesh = mesh2
+
+ elif self.options == Options.TANGENTS:
+ mesh = computeSurfaceTextureCoordinates( polydata )
+ mesh2 = computeNormals( mesh )
+ mesh3: vtkPolyData = computeTangents( mesh2 )
+ self.mesh = mesh3
+
+ else:
+ # Unknown cases and case 0
+ self.mesh = polydata
+
+
+def __generateSurfacicTestCase( options: tuple[ Options, ...] ) -> Iterator[ TriangulatedSurfaceTestCase ]:
+ """Generate test cases with different options.
+
+ Args:
+ options (tuple[Options]): Requested additional feature.
+
+ Yields:
+ Iterator[ TriangulatedSurfaceTestCase ]: Test case containing mesh with requested optional features and expected values.
+ """
+ for opt in options:
+ for i in range( len( pointsCoordsAll ) ):
+ yield TriangulatedSurfaceTestCase( pointsCoordsAll[ i ], trianglesAll[ i ], attributes,
+ expectedNormalsAll[ i ], expectedTangentsAll[ i ],
+ expectedTexturedCoordsAll[ i ], expectedAttributesXYZ[ i ], opt )
+
+
+@pytest.mark.parametrize( "case", __generateSurfacicTestCase( options=( Options.RAW, ) ) )
+def test_computeTextureCoords( case: TriangulatedSurfaceTestCase ) -> None:
+ """Test the computation of texture coordinates."""
+ stc: vtkPolyData = computeSurfaceTextureCoordinates( case.mesh )
+ texturedMap: npt.NDArray[ np.float64 ] = vtk_to_numpy( stc.GetPointData().GetArray( "Texture Coordinates" ) )
+ assert np.allclose( texturedMap, case.expectedTexturedCoords )
+
+
+@pytest.mark.parametrize( "case", __generateSurfacicTestCase( options=( Options.NORMALS_TEXTURED, ) ) )
+def test_computeTangents( case: TriangulatedSurfaceTestCase ) -> None:
+ """Test the computation of tangents."""
+ surfaceWithTangents: vtkPolyData = computeTangents( case.mesh )
+ tangents: npt.NDArray[ np.float64 ] = vtk_to_numpy( surfaceWithTangents.GetCellData().GetTangents() )
+
+ assert np.allclose( tangents, case.expectedTangents[ 0 ] )
+
+
+@pytest.mark.parametrize( "case", __generateSurfacicTestCase( options=( Options.TANGENTS, ) ) )
+def test_getTangents( case: TriangulatedSurfaceTestCase ) -> None:
+ """Test tangents getter."""
+ tangents1: npt.NDArray[ np.float64 ]
+ tangents2: npt.NDArray[ np.float64 ]
+
+ tangents1, tangents2 = getTangentsVectors( case.mesh )
+
+ assert np.allclose( tangents1, case.expectedTangents[ 0 ], rtol=1e-3 )
+ assert np.allclose( tangents2, case.expectedTangents[ 1 ], rtol=1e-3 )
+
+
+@pytest.mark.parametrize( "case", __generateSurfacicTestCase( options=( Options.RAW, ) ) )
+def test_computeNormals( case: TriangulatedSurfaceTestCase ) -> None:
+ """Test the computation of normals."""
+ surfaceWithNormals = computeNormals( case.mesh )
+
+ normals = vtk_to_numpy( surfaceWithNormals.GetCellData().GetNormals() )
+
+ assert np.allclose( normals, case.expectedNormals, rtol=1e-3 )
+
+
+@pytest.mark.parametrize( "case", __generateSurfacicTestCase( options=( Options.NORMALS, ) ) )
+def test_getNormals( case: TriangulatedSurfaceTestCase ) -> None:
+ """Test normals getter."""
+ normals = getNormalVectors( case.mesh )
+
+ assert np.allclose( normals, case.expectedNormals, rtol=1e-3 )
+
+
+@pytest.mark.parametrize( "case",
+ __generateSurfacicTestCase( options=(
+ Options.RAW,
+ Options.NORMALS,
+ Options.TANGENTS,
+ ) ) )
+def test_getLocalBasis( case: TriangulatedSurfaceTestCase ) -> None:
+ """Test local basis getter."""
+ normals, tangents1, tangents2 = getLocalBasisVectors( case.mesh )
+
+ assert np.allclose( normals, case.expectedNormals, rtol=1e-3 )
+ assert np.allclose( tangents1, case.expectedTangents[ 0 ], rtol=1e-3 )
+ assert np.allclose( tangents2, case.expectedTangents[ 1 ], rtol=1e-3 )
+
+
+#########################################################################################
+
+
+@pytest.fixture( scope="function" )
+def emptySurface() -> vtkPolyData:
+ """Generate and return an empty vtkPolyData for tests.
+
+ Returns:
+ vtkPolyData: empty vtkPolyData
+ """
+ return vtkPolyData()
+
+
+def test_failingComputeSurfaceTextureCoords( emptySurface: vtkPolyData ) -> None:
+ """Test VTK error raising of texture coordinate calculation."""
+ with pytest.raises( VTKError ):
+ computeSurfaceTextureCoordinates( emptySurface )
+
+
+def test_failingComputeTangents( emptySurface: vtkPolyData ) -> None:
+ """Test VTK error raising during tangent calculation."""
+ with pytest.raises( VTKError ):
+ computeTangents( emptySurface )
+
+
+def test_failingComputeNormals( emptySurface: vtkPolyData ) -> None:
+ """Test VTK error raising of normals calculation."""
+ with pytest.raises( VTKError ):
+ computeNormals( emptySurface )
+
+
+def test_failingGetTangents( emptySurface: vtkPolyData ) -> None:
+ """Test error raising when getting the surface tangents."""
+ with pytest.raises( ValueError ):
+ getTangentsVectors( emptySurface )
+
+
+def test_failingGetNormals( emptySurface: vtkPolyData ) -> None:
+ """Test error raising when getting the surface normals."""
+ with pytest.raises( ValueError ):
+ getNormalVectors( emptySurface )
+
+
+###############################################################
+
+
+@dataclass( frozen=True )
+class AttributeConversionTestCase:
+ """Test case for attribute conversion from local basis to XYZ basis for one cell."""
+ vector: npt.NDArray[ np.float64 ]
+ normal: npt.NDArray[ np.float64 ]
+ tangent1: npt.NDArray[ np.float64 ]
+ tangent2: npt.NDArray[ np.float64 ]
+ expectedVectorXYZ: npt.NDArray[ np.float64 ]
+
+
+def __generateAttributeConversionTestCase() -> Iterator[ AttributeConversionTestCase ]:
+ """Generator of test cases for the conversion of attributes from local to XYZ attributes."""
+ cases: Iterator[ TriangulatedSurfaceTestCase ] = __generateSurfacicTestCase( options=( Options.RAW, ) )
+ for nc, testcase in enumerate( cases ):
+ if nc == 0:
+ # Try vector size 3, 6 and 9
+ for nvec, localAttribute in enumerate( attributes ):
+ for ncell in range( testcase.mesh.GetNumberOfCells() ):
+
+ yield AttributeConversionTestCase( localAttribute, testcase.expectedNormals[ ncell ],
+ testcase.expectedTangents[ 0 ][ ncell ],
+ testcase.expectedTangents[ 1 ][ ncell ],
+ testcase.expectedAttributeInXYZ[ nvec ][ ncell ] )
+
+
+@pytest.mark.parametrize( "testcase", __generateAttributeConversionTestCase() )
+def test_convertAttributesToXYZ( testcase: AttributeConversionTestCase ) -> None:
+ """Test the conversion of one cell attribute from local to canonic basis."""
+ localAttr: npt.NDArray[ np.float64 ] = testcase.vector
+
+ attrXYZ: npt.NDArray[ np.float64 ] = convertAttributeFromLocalToXYZForOneCell(
+ localAttr, ( testcase.normal, testcase.tangent1, testcase.tangent2 ) )
+ assert np.allclose( attrXYZ, testcase.expectedVectorXYZ, rtol=1e-6 )
From 98a825b6f1f9ae1f28e7cfebf558722463a66f9e Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Wed, 29 Oct 2025 09:18:40 +0100
Subject: [PATCH 12/62] Adding tests for SurfaceGeomechanics filter
---
geos-mesh/tests/conftest.py | 2 +-
.../post_processing/SurfaceGeomechanics.py | 110 ++++++++----------
.../tests/test_SurfaceGeomechanics.py | 84 +++++++++++++
.../geos/pv/plugins/PVSurfaceGeomechanics.py | 7 +-
4 files changed, 133 insertions(+), 70 deletions(-)
create mode 100644 geos-processing/tests/test_SurfaceGeomechanics.py
diff --git a/geos-mesh/tests/conftest.py b/geos-mesh/tests/conftest.py
index 156d37f4c..05fd52216 100644
--- a/geos-mesh/tests/conftest.py
+++ b/geos-mesh/tests/conftest.py
@@ -10,7 +10,7 @@
import numpy.typing as npt
from vtkmodules.vtkCommonDataModel import vtkDataSet, vtkMultiBlockDataSet, vtkPolyData
-from vtkmodules.vtkIOXML import vtkXMLGenericDataObjectReader, vtkXMLMultiBlockDataReader
+from vtkmodules.vtkIOXML import vtkXMLGenericDataObjectReader
@pytest.fixture
diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
index c5dba4d9e..23b68d020 100644
--- a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
+++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
@@ -8,14 +8,9 @@
from typing_extensions import Self, Union
import numpy.typing as npt
-import vtkmodules.util.numpy_support as vnp
from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase
-from vtkmodules.vtkCommonCore import (
- vtkDataArray,
- vtkDoubleArray,
-)
-from vtkmodules.vtkCommonDataModel import (
- vtkPolyData, )
+from vtkmodules.vtkCommonCore import vtkDataArray
+from vtkmodules.vtkCommonDataModel import vtkPolyData
from geos.mesh.utils.arrayModifiers import createAttribute
from geos.mesh.utils.arrayHelpers import (
@@ -38,7 +33,6 @@
GeosMeshOutputsEnum,
PostProcessingOutputsEnum,
)
-import geos.utils.geometryFunctions as geom
__doc__ = """
SurfaceGeomechanics is a VTK filter that allows:
@@ -71,24 +65,24 @@
speHandler: bool # defaults to false
# Instantiate the filter
- filter: SurfaceGeomechanics = SurfaceGeomechanics( inputMesh, speHandler )
+ sg: SurfaceGeomechanics = SurfaceGeomechanics( inputMesh, speHandler )
# Set the handler of yours (only if speHandler is True).
yourHandler: logging.Handler
- filter.SetLoggerHandler( yourHandler )
+ sg.SetLoggerHandler( yourHandler )
# [optional] Set rock cohesion and friction angle
- filter.SetRockCohesion( rockCohesion )
- filter.SetFrictionAngle( frictionAngle )
+ sg.SetRockCohesion( rockCohesion )
+ sg.SetFrictionAngle( frictionAngle )
# Do calculations
- filter.applyFilter()
+ sg.applyFilter()
# Get output object
- output: vtkPolyData = filter.GetOutputMesh()
+ output: vtkPolyData = sg.GetOutputMesh()
# Get created attribute names
- newAttributeNames: set[str] = filter.GetNewAttributeNames()
+ newAttributeNames: set[str] = sg.GetNewAttributeNames()
.. Note::
@@ -100,11 +94,10 @@
"""
loggerTitle: str = "Surface Geomechanics"
+
class SurfaceGeomechanics( VTKPythonAlgorithmBase ):
- def __init__( self: Self,
- surfacicMesh: vtkPolyData,
- speHandler: bool = False ) -> None:
+ def __init__( self: Self, surfacicMesh: vtkPolyData, speHandler: bool = False ) -> None:
"""Vtk filter to compute geomechanical surfacic attributes.
Input and Output objects are a vtkPolydata with surfaces
@@ -126,18 +119,18 @@ def __init__( self: Self,
self.logger.setLevel( logging.INFO )
# Input surfacic mesh
- if surfacicMesh is None:
- self.logger.error( "Input surface is undefined." )
if not surfacicMesh.IsA( "vtkPolyData" ):
self.logger.error( f"Input surface is expected to be a vtkPolyData, not a {type(surfacicMesh)}." )
self.inputMesh: vtkPolyData = surfacicMesh
# Identification of the input surface (logging purpose)
- self.name = None
+ self.name: Union[ str, None ] = None
# Output surfacic mesh
self.outputMesh: vtkPolyData
# Default attributes to convert from local to XYZ
self.convertAttributesOn: bool = True
- self.attributesToConvert: set[ str ] = { GeosMeshOutputsEnum.DISPLACEMENT_JUMP.attributeName, }
+ self.attributesToConvert: set[ str ] = {
+ GeosMeshOutputsEnum.DISPLACEMENT_JUMP.attributeName,
+ }
# Attributes are either on points or on cells
self.attributeOnPoints: bool = False
@@ -148,7 +141,6 @@ def __init__( self: Self,
# New created attributes names
self.newAttributeNames: set[ str ] = set()
-
def SetLoggerHandler( self: Self, handler: Logger ) -> None:
"""Set a specific handler for the filter logger.
@@ -164,7 +156,7 @@ def SetLoggerHandler( self: Self, handler: Logger ) -> None:
"The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization."
)
- def SetSurfaceName( self: Self, name: str ):
+ def SetSurfaceName( self: Self, name: str ) -> None:
"""Set a name for the input surface. For logging purpose only.
Args:
@@ -188,17 +180,14 @@ def SetFrictionAngle( self: Self, frictionAngle: float ) -> None:
"""
self.frictionAngle = frictionAngle
-
def ConvertAttributesOn( self: Self ) -> None:
"""Activate the conversion of attributes from local to XYZ basis."""
self.convertAttributesOn = True
-
def ConvertAttributesOff( self: Self ) -> None:
"""Deactivate the conversion of attributes from local to XYZ bais."""
self.convertAttributesOn = False
-
def GetConvertAttributes( self: Self ) -> bool:
"""If convertAttributesOn is True, the set of attributes will be converted from local to XYZ during filter application. Default is True.
@@ -207,7 +196,6 @@ def GetConvertAttributes( self: Self ) -> bool:
"""
return self.convertAttributesOn
-
def GetAttributesToConvert( self: Self ) -> set[ str ]:
"""Get the set of attributes that will be converted from local to XYZ basis.
@@ -216,7 +204,6 @@ def GetAttributesToConvert( self: Self ) -> set[ str ]:
"""
return self.attributesToConvert
-
def SetAttributesToConvert( self: Self, attributesToConvert: set[ str ] ) -> None:
"""Set the list of attributes that will be converted from local to XYZ basis.
@@ -230,17 +217,15 @@ def SetAttributesToConvert( self: Self, attributesToConvert: set[ str ] ) -> Non
self.ConvertAttributesOff()
self.logger.warning( "Empty set of attributes to convert." )
-
def AddAttributesToConvert( self: Self, attributeName: Union[ list[ str ], set[ str ] ] ) -> None:
"""Add an attribute to the set of attributes to convert.
Args:
- attributeName (Union[ list[str],set[ str]]): List of the attribute array names.
+ attributeName (Union[list[str],set[str]]): List of the attribute array names.
"""
- self.attributesToConvert.add( attributeName )
+ self.attributesToConvert.update( attributeName )
self.ConvertAttributesOn()
-
def GetNewAttributeNames( self: Self ) -> set[ str ]:
"""Get the set of new attribute names that were created.
@@ -249,7 +234,6 @@ def GetNewAttributeNames( self: Self ) -> set[ str ]:
"""
return self.newAttributeNames
-
def applyFilter( self: Self ) -> bool:
"""Compute Geomechanical properties on input surface.
@@ -269,7 +253,7 @@ def applyFilter( self: Self ) -> bool:
# Conversion of attributes from Normal/Tangent basis to xyz basis
if self.convertAttributesOn:
- self.logger.info( "Conversion of attributes from local to XYZ basis.")
+ self.logger.info( "Conversion of attributes from local to XYZ basis." )
if not self.convertAttributesFromLocalToXYZBasis():
self.logger.error( "Error while converting attributes from local to XYZ basis." )
return False
@@ -282,7 +266,6 @@ def applyFilter( self: Self ) -> bool:
self.logger.info( f"Filter {self.logger.name} successfully applied on surface {self.name}." )
return True
-
def convertAttributesFromLocalToXYZBasis( self: Self ) -> bool:
"""Convert attributes from local to XYZ basis.
@@ -293,7 +276,7 @@ def convertAttributesFromLocalToXYZBasis( self: Self ) -> bool:
attributesToConvert: set[ str ] = self.__filterAttributesToConvert()
if len( attributesToConvert ) == 0:
- self.logger.warning( f"No attribute to convert from local to XYZ basis were found." )
+ self.logger.warning( "No attribute to convert from local to XYZ basis were found." )
return True
# Do conversion from local to XYZ for each attribute
@@ -305,25 +288,25 @@ def convertAttributesFromLocalToXYZBasis( self: Self ) -> bool:
continue
if self.attributeOnPoints:
- self.logger.error( "This filter can only convert cell attributes from local to XYZ basis, not point attributes." )
- localArray: vtkDoubleArray = getArrayInObject( self.outputMesh, attrNameLocal, self.attributeOnPoints)
+ self.logger.error(
+ "This filter can only convert cell attributes from local to XYZ basis, not point attributes." )
+ localArray: npt.NDArray[ np.float64 ] = getArrayInObject( self.outputMesh, attrNameLocal,
+ self.attributeOnPoints )
arrayXYZ: npt.NDArray[ np.float64 ] = self.__computeXYZCoordinates( localArray )
# Create converted attribute array in dataset
- if createAttribute(
- self.outputMesh,
- arrayXYZ,
- attrNameXYZ,
- ComponentNameEnum.XYZ.value,
- onPoints = self.attributeOnPoints,
- logger = self.logger):
+ if createAttribute( self.outputMesh,
+ arrayXYZ,
+ attrNameXYZ,
+ ComponentNameEnum.XYZ.value,
+ onPoints=self.attributeOnPoints,
+ logger=self.logger ):
self.logger.info( f"Attribute {attrNameXYZ} added to the output mesh." )
self.newAttributeNames.add( attrNameXYZ )
return True
-
def __filterAttributesToConvert( self: Self ) -> set[ str ]:
"""Filter the set of attribute names if they are vectorial and present.
@@ -340,9 +323,11 @@ def __filterAttributesToConvert( self: Self ) -> set[ str ]:
if attr.GetNumberOfComponents() > 2:
attributesFiltered.add( attrName )
else:
- self.logger.warning( f"Attribute {attrName} filtered out. Dimension of the attribute must be equal or greater than 3." )
+ self.logger.warning(
+ f"Attribute {attrName} filtered out. Dimension of the attribute must be equal or greater than 3."
+ )
else:
- self.logger.warning( f"Attribute {attrName} not in the input mesh.")
+ self.logger.warning( f"Attribute {attrName} not in the input mesh." )
if len( attributesFiltered ) == 0:
self.logger.warning( "All attributes filtered out." )
@@ -370,7 +355,9 @@ def __computeXYZCoordinates(
for i, cellAttribute in enumerate( attrArray ):
if len( cellAttribute ) not in ( 3, 6, 9 ):
- raise ValueError( f"Inconsistent number of components for attribute. Expected 3, 6 or 9 but got { len( cellAttribute.shape ) }." )
+ raise ValueError(
+ f"Inconsistent number of components for attribute. Expected 3, 6 or 9 but got { len( cellAttribute.shape ) }."
+ )
# Compute attribute XYZ components
cellLocalBasis: npt.NDArray[ np.float64 ] = localBasis[ :, i, : ]
@@ -381,7 +368,6 @@ def __computeXYZCoordinates(
return attrXYZ
-
def computeShearCapacityUtilization( self: Self ) -> bool:
"""Compute the shear capacity utilization (SCU) on surface.
@@ -395,27 +381,23 @@ def computeShearCapacityUtilization( self: Self ) -> bool:
tractionAttributeName: str = GeosMeshOutputsEnum.TRACTION.attributeName
traction: npt.NDArray[ np.float64 ] = getArrayInObject( self.outputMesh, tractionAttributeName,
self.attributeOnPoints )
- if traction is None:
- self.logger.error( f"{tractionAttributeName} attribute is undefined." )
- self.logger.error( f"Failed to compute {SCUAttributeName}." )
# Computation of the shear capacity utilization (SCU)
- scuAttribute: npt.NDArray[ np.float64 ] = fcts.shearCapacityUtilization(
- traction, self.rockCohesion, self.frictionAngle )
+ # TODO: better handling of errors in shearCapacityUtilization
+ try:
+ scuAttribute: npt.NDArray[ np.float64 ] = fcts.shearCapacityUtilization(
+ traction, self.rockCohesion, self.frictionAngle )
+ except AssertionError:
+ self.logger.error( f"Failed to compute {SCUAttributeName}." )
+ return False
# Create attribute
if not createAttribute(
- self.outputMesh,
- scuAttribute,
- SCUAttributeName,
- (),
- self.attributeOnPoints,
- logger = self.logger
- ):
+ self.outputMesh, scuAttribute, SCUAttributeName, (), self.attributeOnPoints, logger=self.logger ):
self.logger.error( f"Failed to create attribute {SCUAttributeName}." )
return False
else:
- self.logger.info( f"SCU computed and added to the output mesh." )
+ self.logger.info( "SCU computed and added to the output mesh." )
self.newAttributeNames.add( SCUAttributeName )
return True
diff --git a/geos-processing/tests/test_SurfaceGeomechanics.py b/geos-processing/tests/test_SurfaceGeomechanics.py
new file mode 100644
index 000000000..a940f9039
--- /dev/null
+++ b/geos-processing/tests/test_SurfaceGeomechanics.py
@@ -0,0 +1,84 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: Paloma Martinez
+# SPDX-License-Identifier: Apache 2.0
+# ruff: noqa: E402 # disable Module level import not at top of file
+
+import pytest
+from typing_extensions import Self, Union
+import numpy.typing as npt
+import numpy as np
+
+from dataclasses import dataclass, field
+from vtkmodules.vtkCommonDataModel import vtkPolyData, vtkTriangle, vtkCellArray
+from vtkmodules.vtkCommonCore import vtkPoints, vtkFloatArray
+import vtkmodules.util.numpy_support as vnp
+from geos.processing.post_processing.SurfaceGeomechanics import SurfaceGeomechanics
+
+
+@dataclass
+class TriangulatedSurfaceTestCase:
+ pointsCoords: npt.NDArray[ np.float64 ]
+ triangles: list[ tuple[ int, int, int ] ]
+ mesh: vtkPolyData = field( init=False )
+ attributes: Union[ None, dict[ str, npt.NDArray ] ]
+
+ def __post_init__( self: Self ) -> None:
+ """Generate the mesh."""
+ vtk_points = vtkPoints()
+ for coord in self.pointsCoords:
+ vtk_points.InsertNextPoint( coord )
+
+ vtk_triangles = vtkCellArray()
+ for t in self.triangles:
+ triangle = vtkTriangle()
+ triangle.GetPointIds().SetId( 0, t[ 0 ] )
+ triangle.GetPointIds().SetId( 1, t[ 1 ] )
+ triangle.GetPointIds().SetId( 2, t[ 2 ] )
+ vtk_triangles.InsertNextCell( triangle )
+
+ polydata = vtkPolyData()
+ polydata.SetPoints( vtk_points )
+ polydata.SetPolys( vtk_triangles )
+
+ self.mesh = polydata
+
+ if self.attributes is not None:
+ for attrName, attrValue in self.attributes.items():
+ newAttribute: vtkFloatArray = vnp.numpy_to_vtk( attrValue, deep=True )
+ newAttribute.SetName( attrName )
+
+ self.mesh.GetCellData().AddArray( newAttribute )
+ self.mesh.Modified()
+
+ assert self.mesh.GetCellData().HasArray( attrName )
+
+
+# yapf: disable
+pointsCoords: npt.NDArray[ np.float64 ] = np.array( [ [ 0, 0, 0 ], [ 0, 1, 0 ], [ 0, 2, 0 ], [ 0, 2, 1 ], [ 1, 1, 1.5 ], [ 0, 0, 1 ] ] )
+triangles: list[ tuple[ int, int, int ] ] = [ ( 0, 1, 5 ), ( 1, 4, 5 ), ( 1, 2, 3 ), ( 1, 4, 3 ) ]
+attributes: dict[ str, npt.NDArray ] = { "traction": np.array( [ [ -9e-10, 3e-15, 0.0 ], [ -9e-10, 3e-15, 0.0 ], [ 0.0, 3e-15, -3e-17 ], [ 0.0, 3e-15, -3e-17 ], ] ),
+ "displacementJump": np.array( [ [ 4e-02, 8e-07, 3e-08 ], [ 4e-02, 8e-07, 3e-08 ], [ 2e-02, 4e-07, -8e-08 ], [ 2e-02, 4e-07, -8e-08 ], ] )
+}
+# yapf: enable
+
+
+def test_SurfaceGeomechanics() -> None:
+ """Test SurfaceGeomechanics vtk filter."""
+ testCase: TriangulatedSurfaceTestCase = TriangulatedSurfaceTestCase( pointsCoords, triangles, attributes )
+
+ filter: SurfaceGeomechanics = SurfaceGeomechanics( testCase.mesh )
+
+ assert filter.applyFilter()
+
+ mesh: vtkPolyData = filter.GetOutputMesh()
+ assert mesh.GetCellData().HasArray( "SCU" )
+ assert mesh.GetCellData().HasArray( "displacementJump_XYZ" )
+
+
+def test_failingSurfaceGeomechanics() -> None:
+ """Test failing of SurfaceGeomechanics due to absence of attributes in the mesh."""
+ failingCase: TriangulatedSurfaceTestCase = TriangulatedSurfaceTestCase( pointsCoords, triangles, None )
+ filter: SurfaceGeomechanics = SurfaceGeomechanics( failingCase.mesh )
+ with pytest.raises( AssertionError ):
+ assert filter.applyFilter()
diff --git a/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
index ff198ba2d..a5a2a26cd 100644
--- a/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
+++ b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
@@ -11,8 +11,7 @@
VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
)
from paraview.detail.loghandler import ( # type: ignore[import-not-found]
- VTKHandler,
-)
+ VTKHandler, )
# update sys.path to load all GEOS Python Package dependencies
geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent
@@ -30,9 +29,6 @@
getBlockElementIndexesFlatten,
getBlockFromFlatIndex,
)
-from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
- VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
-)
from vtkmodules.vtkCommonCore import (
vtkDataArray,
vtkInformation,
@@ -64,6 +60,7 @@
"""
+
@smproxy.filter( name="PVSurfaceGeomechanics", label="Geos Surface Geomechanics" )
@smhint.xml( '' )
@smproperty.input( name="Input", port_index=0 )
From 52bcd566c4de16537a849b9141293d6f106f4f5d Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Wed, 29 Oct 2025 10:12:38 +0100
Subject: [PATCH 13/62] Fix
---
docs/geos_posp_docs/PVplugins.rst | 6 ------
docs/geos_posp_docs/filters.rst | 8 --------
docs/geos_processing_docs/generic_processing_tool.rst | 4 ----
.../src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py | 2 +-
.../PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py | 2 +-
install_packages.sh | 2 +-
6 files changed, 3 insertions(+), 21 deletions(-)
delete mode 100644 docs/geos_processing_docs/generic_processing_tool.rst
diff --git a/docs/geos_posp_docs/PVplugins.rst b/docs/geos_posp_docs/PVplugins.rst
index 8f41728be..391c26323 100644
--- a/docs/geos_posp_docs/PVplugins.rst
+++ b/docs/geos_posp_docs/PVplugins.rst
@@ -62,9 +62,3 @@ PVMohrCirclePlot plugin
---------------------------------
.. automodule:: PVplugins.PVMohrCirclePlot
-
-PVplugins.PVSurfaceGeomechanics module
---------------------------------------
-
-.. automodule:: PVplugins.PVSurfaceGeomechanics
-
diff --git a/docs/geos_posp_docs/filters.rst b/docs/geos_posp_docs/filters.rst
index b3c3cd892..f0c00c1cb 100644
--- a/docs/geos_posp_docs/filters.rst
+++ b/docs/geos_posp_docs/filters.rst
@@ -18,11 +18,3 @@ geos_posp.filters.GeosBlockMerge module
:members:
:undoc-members:
:show-inheritance:
-
-geos_posp.filters.SurfaceGeomechanics module
-------------------------------------------------
-
-.. automodule:: geos_posp.filters.SurfaceGeomechanics
- :members:
- :undoc-members:
- :show-inheritance:
diff --git a/docs/geos_processing_docs/generic_processing_tool.rst b/docs/geos_processing_docs/generic_processing_tool.rst
deleted file mode 100644
index 4fdb32fcb..000000000
--- a/docs/geos_processing_docs/generic_processing_tool.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Generic processing filters
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-In progress..
\ No newline at end of file
diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py
index 8b5596883..03024b833 100644
--- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py
+++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurface.py
@@ -36,7 +36,7 @@
from PVplugins.PVExtractMergeBlocksVolumeSurface import (
PVExtractMergeBlocksVolumeSurface, )
from geos.processing.post_processing.GeomechanicsCalculator import GeomechanicsCalculator
-from PVplugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics
+from geos.pv.plugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics
__doc__ = """
PVGeomechanicsWorkflowVolumeSurface is a Paraview plugin that execute
diff --git a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py
index 715de74c0..52cae47b7 100644
--- a/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py
+++ b/geos-posp/src/PVplugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py
@@ -36,7 +36,7 @@
from PVplugins.PVExtractMergeBlocksVolumeSurfaceWell import (
PVExtractMergeBlocksVolumeSurfaceWell, )
from geos.processing.post_processing.GeomechanicsCalculator import GeomechanicsCalculator
-from PVplugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics
+from geos.pv.plugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics
__doc__ = """
PVGeomechanicsWorkflowVolumeSurfaceWell is a Paraview plugin that execute
diff --git a/install_packages.sh b/install_packages.sh
index e71602286..b6726ef71 100755
--- a/install_packages.sh
+++ b/install_packages.sh
@@ -2,8 +2,8 @@
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-posp
python -m pip install --upgrade ./geos-xml-tools
python -m pip install --upgrade ./geos-xml-viewer
python -m pip install --upgrade ./hdf5-wrapper
From d3e4fc6fbab6df3ff66c5aa979429aa0438b1986 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Wed, 29 Oct 2025 11:33:26 +0100
Subject: [PATCH 14/62] typo
---
geos-utils/tests/test_algebraFunctions.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/geos-utils/tests/test_algebraFunctions.py b/geos-utils/tests/test_algebraFunctions.py
index 7d3effbaf..104a8136b 100644
--- a/geos-utils/tests/test_algebraFunctions.py
+++ b/geos-utils/tests/test_algebraFunctions.py
@@ -5,7 +5,7 @@
import numpy as np
import numpy.typing as npt
from unittest import TestCase
-from typing.extensions import Self
+from typing_extensions import Self
from geos.utils.algebraFunctions import (
getAttributeMatrixFromVector,
From badc7cd7227d206f8f779c9bc67cafa22ef519c7 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Thu, 30 Oct 2025 10:15:55 +0100
Subject: [PATCH 15/62] Small modifications from Romain review
---
docs/geos_processing_docs/post_processing.rst | 2 ++
.../post_processing/SurfaceGeomechanics.py | 11 +++++++----
.../tests/test_SurfaceGeomechanics.py | 10 +++++-----
.../geos/pv/plugins/PVSurfaceGeomechanics.py | 18 +++++++++---------
4 files changed, 23 insertions(+), 18 deletions(-)
diff --git a/docs/geos_processing_docs/post_processing.rst b/docs/geos_processing_docs/post_processing.rst
index 8698539a7..d22b46a0e 100644
--- a/docs/geos_processing_docs/post_processing.rst
+++ b/docs/geos_processing_docs/post_processing.rst
@@ -13,6 +13,8 @@ GEOS computes many outputs including flow and geomechanic properties if coupled
Several processing filters require the definition of physical parameters. The following list is non-exhaustive.
Default values:
+ - grainBulkModulus = 38e9 Pa ( quartz value )
+ - specificDensity = 1000.0 kg/m³ ( water value )
- rock cohesion: 0.0 Pa $( fractured case )
- friction angle: 10°
diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
index 23b68d020..2f527efd2 100644
--- a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
+++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Martin Lemay
+# SPDX-FileContributor: Martin Lemay, Paloma Martinez
# ruff: noqa: E402 # disable Module level import not at top of file
import logging
import numpy as np
@@ -86,16 +86,19 @@
.. Note::
+
By default, conversion of attributes from local to XYZ basis is performed for the following list: { 'displacementJump' }.
This list can be modified in different ways:
- - Addition of one or several additional attributes to the set by using the filter function `AddAttributesToConvert`.
- - Replace the list completely with the function `SetAttributesToConvert`.
+
+ - Addition of one or several additional attributes to the set by using the filter function `AddAttributesToConvert`.
+ - Replace the list completely with the function `SetAttributesToConvert`.
+
Note that the dimension of the attributes to convert must be equal or greater than 3.
"""
loggerTitle: str = "Surface Geomechanics"
-class SurfaceGeomechanics( VTKPythonAlgorithmBase ):
+class SurfaceGeomechanics:
def __init__( self: Self, surfacicMesh: vtkPolyData, speHandler: bool = False ) -> None:
"""Vtk filter to compute geomechanical surfacic attributes.
diff --git a/geos-processing/tests/test_SurfaceGeomechanics.py b/geos-processing/tests/test_SurfaceGeomechanics.py
index a940f9039..7623e802e 100644
--- a/geos-processing/tests/test_SurfaceGeomechanics.py
+++ b/geos-processing/tests/test_SurfaceGeomechanics.py
@@ -67,11 +67,11 @@ def test_SurfaceGeomechanics() -> None:
"""Test SurfaceGeomechanics vtk filter."""
testCase: TriangulatedSurfaceTestCase = TriangulatedSurfaceTestCase( pointsCoords, triangles, attributes )
- filter: SurfaceGeomechanics = SurfaceGeomechanics( testCase.mesh )
+ sgFilter: SurfaceGeomechanics = SurfaceGeomechanics( testCase.mesh )
- assert filter.applyFilter()
+ assert sgFilter.applyFilter()
- mesh: vtkPolyData = filter.GetOutputMesh()
+ mesh: vtkPolyData = sgFilter.GetOutputMesh()
assert mesh.GetCellData().HasArray( "SCU" )
assert mesh.GetCellData().HasArray( "displacementJump_XYZ" )
@@ -79,6 +79,6 @@ def test_SurfaceGeomechanics() -> None:
def test_failingSurfaceGeomechanics() -> None:
"""Test failing of SurfaceGeomechanics due to absence of attributes in the mesh."""
failingCase: TriangulatedSurfaceTestCase = TriangulatedSurfaceTestCase( pointsCoords, triangles, None )
- filter: SurfaceGeomechanics = SurfaceGeomechanics( failingCase.mesh )
+ sgFilter: SurfaceGeomechanics = SurfaceGeomechanics( failingCase.mesh )
with pytest.raises( AssertionError ):
- assert filter.applyFilter()
+ assert sgFilter.applyFilter()
diff --git a/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
index a5a2a26cd..50e7dc34f 100644
--- a/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
+++ b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
@@ -154,19 +154,19 @@ def RequestData(
for blockIndex in surfaceBlockIndexes:
surfaceBlock: vtkPolyData = vtkPolyData.SafeDownCast( getBlockFromFlatIndex( output, blockIndex ) )
- filter: SurfaceGeomechanics = SurfaceGeomechanics( surfaceBlock, True )
- filter.SetSurfaceName( f"blockIndex {blockIndex}" )
- if not filter.logger.hasHandlers():
- filter.SetLoggerHandler( VTKHandler() )
+ sgFilter: SurfaceGeomechanics = SurfaceGeomechanics( surfaceBlock, True )
+ sgFilter.SetSurfaceName( f"blockIndex {blockIndex}" )
+ if not sgFilter.logger.hasHandlers():
+ sgFilter.SetLoggerHandler( VTKHandler() )
- filter.SetRockCohesion( self._getRockCohesion() )
- filter.SetFrictionAngle( self._getFrictionAngle() )
- filter.applyFilter()
+ sgFilter.SetRockCohesion( self._getRockCohesion() )
+ sgFilter.SetFrictionAngle( self._getFrictionAngle() )
+ sgFilter.applyFilter()
- outputSurface: vtkPolyData = filter.GetOutputMesh()
+ outputSurface: vtkPolyData = sgFilter.GetOutputMesh()
# add attributes to output surface mesh
- for attributeName in filter.GetNewAttributeNames():
+ for attributeName in sgFilter.GetNewAttributeNames():
attr: vtkDataArray = outputSurface.GetCellData().GetArray( attributeName )
surfaceBlock.GetCellData().AddArray( attr )
surfaceBlock.GetCellData().Modified()
From 335782c2898585fc43f7526d0e9a44475c332e4b Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Fri, 31 Oct 2025 11:15:10 +0100
Subject: [PATCH 16/62] Fix typing and error in getTangents function
---
.../src/geos/mesh/utils/genericHelpers.py | 24 +++++++++----------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/geos-mesh/src/geos/mesh/utils/genericHelpers.py b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
index 5297c960e..e4d665068 100644
--- a/geos-mesh/src/geos/mesh/utils/genericHelpers.py
+++ b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
@@ -6,7 +6,7 @@
import numpy.typing as npt
from typing import Iterator, List, Sequence, Any, Union, Tuple
from vtkmodules.util.numpy_support import ( numpy_to_vtk, vtk_to_numpy )
-from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints, reference, vtkDataArray, vtkLogger
+from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints, reference, vtkDataArray, vtkLogger, vtkFloatArray
from vtkmodules.vtkCommonDataModel import (
vtkUnstructuredGrid,
vtkMultiBlockDataSet,
@@ -353,19 +353,19 @@ def getTangentsVectors( surface: vtkPolyData ) -> Tuple[ npt.NDArray[ np.float64
Tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: The tangents vectors of the input surface.
"""
# Get first tangential component
- tangents1: Union[ npt.NDArray[ np.float64 ],
- None ] = surface.GetCellData().GetTangents() # type: ignore[no-untyped-call]
- if tangents1 is None:
- raise ValueError( "No tangential attribute found in the mesh. Use the computeTangents function beforehand." )
- else:
- tangents1 = vtk_to_numpy( tangents1 )
+ vtkTangents: Union[ vtkFloatArray, None ] = surface.GetCellData().GetTangents() # type: ignore[no-untyped-call]
+ tangents1: npt.NDArray[ np.float64 ]
- # Compute second tangential component
- normals: npt.NDArray[ np.float64 ] = getNormalVectors( surface )
+ try:
+ tangents1 = vtk_to_numpy( vtkTangents )
+ except AttributeError as err:
+ print( "No tangential attribute found in the mesh. Use the computeTangents function beforehand." )
+ raise VTKError( err ) from err
+ else:
+ # Compute second tangential component
+ normals: npt.NDArray[ np.float64 ] = getNormalVectors( surface )
- tangents2: Union[ npt.NDArray[ np.float64 ], None ] = np.cross( normals, tangents1, axis=1 ).astype( np.float64 )
- if tangents2 is None:
- raise ValueError( "Local basis third axis was not computed." )
+ tangents2: npt.NDArray[ np.float64 ] = np.cross( normals, tangents1, axis=1 ).astype( np.float64 )
return ( tangents1, tangents2 )
From 359ceb024e17f450454f2dc6ed5f8e0bd01ec389 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Fri, 31 Oct 2025 13:16:40 +0100
Subject: [PATCH 17/62] Fix tests following previous commit modifs
---
geos-mesh/src/geos/mesh/utils/genericHelpers.py | 2 +-
geos-mesh/tests/test_genericHelpers_normalAndTangents.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/geos-mesh/src/geos/mesh/utils/genericHelpers.py b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
index e4d665068..fd592984c 100644
--- a/geos-mesh/src/geos/mesh/utils/genericHelpers.py
+++ b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
@@ -393,7 +393,7 @@ def getLocalBasisVectors( surface: vtkPolyData ) -> npt.NDArray[ np.float64 ]:
tangents: Tuple[ npt.NDArray[ np.float64 ],
npt.NDArray[ np.float64 ] ] = getTangentsVectors( surfaceWithNormals )
# If no tangents is present in the mesh, they are computed on that surface
- except ValueError:
+ except VTKError:
surfaceWithTangents: vtkPolyData = computeTangents( surfaceWithNormals )
tangents = getTangentsVectors( surfaceWithTangents )
diff --git a/geos-mesh/tests/test_genericHelpers_normalAndTangents.py b/geos-mesh/tests/test_genericHelpers_normalAndTangents.py
index 22acccc4a..5cfc3f276 100644
--- a/geos-mesh/tests/test_genericHelpers_normalAndTangents.py
+++ b/geos-mesh/tests/test_genericHelpers_normalAndTangents.py
@@ -254,7 +254,7 @@ def test_failingComputeNormals( emptySurface: vtkPolyData ) -> None:
def test_failingGetTangents( emptySurface: vtkPolyData ) -> None:
"""Test error raising when getting the surface tangents."""
- with pytest.raises( ValueError ):
+ with pytest.raises( VTKError ):
getTangentsVectors( emptySurface )
From 27ae09d9a624e45b95b7a18354dc622672a5b58e Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Fri, 31 Oct 2025 14:03:49 +0100
Subject: [PATCH 18/62] SISO filter
---
.../post_processing/SurfaceGeomechanics.py | 1 -
.../geos/pv/plugins/PVSurfaceGeomechanics.py | 57 ++++++-------------
2 files changed, 16 insertions(+), 42 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
index 2f527efd2..d49db1b9e 100644
--- a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
+++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
@@ -111,7 +111,6 @@ def __init__( self: Self, surfacicMesh: vtkPolyData, speHandler: bool = 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="vtkPolyData" ) # type: ignore[no-untyped-call]
# Logger
self.logger: Logger
diff --git a/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
index 50e7dc34f..2858766ec 100644
--- a/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
+++ b/geos-pv/src/geos/pv/plugins/PVSurfaceGeomechanics.py
@@ -8,7 +8,7 @@
from typing_extensions import Self
from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
- VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
+ VTKPythonAlgorithmBase, smdomain, smproperty,
)
from paraview.detail.loghandler import ( # type: ignore[import-not-found]
VTKHandler, )
@@ -17,6 +17,7 @@
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
+from geos.pv.utils.details import ( SISOFilter, FilterCategory )
update_paths()
@@ -30,10 +31,7 @@
getBlockFromFlatIndex,
)
from vtkmodules.vtkCommonCore import (
- vtkDataArray,
- vtkInformation,
- vtkInformationVector,
-)
+ vtkDataArray, )
from vtkmodules.vtkCommonDataModel import (
vtkMultiBlockDataSet,
vtkPolyData,
@@ -61,24 +59,16 @@
"""
-@smproxy.filter( name="PVSurfaceGeomechanics", label="Geos Surface Geomechanics" )
-@smhint.xml( '' )
-@smproperty.input( name="Input", port_index=0 )
-@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
+@SISOFilter( category=FilterCategory.GEOS_GEOMECHANICS,
+ decoratedLabel="Geos Surface Geomechanics",
+ decoratedType="vtkMultiBlockDataSet" )
class PVSurfaceGeomechanics( VTKPythonAlgorithmBase ):
def __init__( self: Self ) -> None:
"""Compute additional geomechanical surface outputs.
- Input is a vtkMultiBlockDataSet that contains surfaces with
- Normals and Tangential attributes.
+ Input is a vtkMultiBlockDataSet containing surfaces.
"""
- super().__init__(
- nInputPorts=1,
- nOutputPorts=1,
- inputType="vtkMultiBlockDataSet",
- outputType="vtkMultiBlockDataSet",
- )
# rock cohesion (Pa)
self.rockCohesion: float = DEFAULT_ROCK_COHESION
# friction angle (°)
@@ -118,7 +108,7 @@ def a01SetRockCohesion( self: Self, value: float ) -> None:
""" )
def a02SetFrictionAngle( self: Self, value: float ) -> None:
- """Set frition angle.
+ """Set friction angle.
Args:
value (float): friction angle (°)
@@ -126,33 +116,18 @@ def a02SetFrictionAngle( self: Self, value: float ) -> None:
self.frictionAngle = value
self.Modified()
- def RequestData(
- self: Self,
- request: vtkInformation, # noqa: F841
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestData.
+ def Filter( self: Self, inputMesh: vtkMultiBlockDataSet, outputMesh: vtkMultiBlockDataSet ) -> None:
+ """Apply SurfaceGeomechanics filter to the mesh.
Args:
- request (vtkInformation): Request
- inInfoVec (list[vtkInformationVector]): Input objects
- outInfoVec (vtkInformationVector): Output objects
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
+ inputMesh (vtkMultiBlockDataSet): The input multiblock mesh with surfaces.
+ outputMesh (vtkMultiBlockDataSet): The output multiblock mesh with converted attributes and SCU.
"""
- inputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
- output: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
-
- assert inputMesh is not None, "Input surface is null."
- assert output is not None, "Output pipeline is null."
-
- output.ShallowCopy( inputMesh )
+ outputMesh.ShallowCopy( inputMesh )
surfaceBlockIndexes: list[ int ] = getBlockElementIndexesFlatten( inputMesh )
for blockIndex in surfaceBlockIndexes:
- surfaceBlock: vtkPolyData = vtkPolyData.SafeDownCast( getBlockFromFlatIndex( output, blockIndex ) )
+ surfaceBlock: vtkPolyData = vtkPolyData.SafeDownCast( getBlockFromFlatIndex( outputMesh, blockIndex ) )
sgFilter: SurfaceGeomechanics = SurfaceGeomechanics( surfaceBlock, True )
sgFilter.SetSurfaceName( f"blockIndex {blockIndex}" )
@@ -172,8 +147,8 @@ def RequestData(
surfaceBlock.GetCellData().Modified()
surfaceBlock.Modified()
- output.Modified()
- return 1
+ outputMesh.Modified()
+ return
def _getFrictionAngle( self: Self ) -> float:
"""Get friction angle in radian.
From 58e30e332ee8e3ba9ffd447200b678611dd81222 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Fri, 31 Oct 2025 14:10:00 +0100
Subject: [PATCH 19/62] fix docstring
---
geos-mesh/src/geos/mesh/utils/genericHelpers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/geos-mesh/src/geos/mesh/utils/genericHelpers.py b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
index fd592984c..0e2ebb39e 100644
--- a/geos-mesh/src/geos/mesh/utils/genericHelpers.py
+++ b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
@@ -377,7 +377,7 @@ def getLocalBasisVectors( surface: vtkPolyData ) -> npt.NDArray[ np.float64 ]:
surface(vtkPolydata): The input surface.
Returns:
- Tuple[npt.NDArray[np.float64], npt.NDArray[np.float64], npt.NDArray[np.float64]]: Array with normal, tangential 1 and tangential 2 vectors.
+ npt.NDArray[np.float64]: Array with normal, tangential 1 and tangential 2 vectors.
"""
try:
normals: npt.NDArray[ np.float64 ] = getNormalVectors( surface )
From c56b2de25c2c8a4b8cf4d52eba36a879094ce028 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Fri, 31 Oct 2025 14:44:47 +0100
Subject: [PATCH 20/62] fix the fix
---
.../src/geos/processing/post_processing/SurfaceGeomechanics.py | 2 --
geos-processing/tests/test_SurfaceGeomechanics.py | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
index d49db1b9e..90320e826 100644
--- a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
+++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
@@ -8,7 +8,6 @@
from typing_extensions import Self, Union
import numpy.typing as npt
-from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase
from vtkmodules.vtkCommonCore import vtkDataArray
from vtkmodules.vtkCommonDataModel import vtkPolyData
@@ -111,7 +110,6 @@ def __init__( self: Self, surfacicMesh: vtkPolyData, speHandler: bool = False )
speHandler (bool, optional): True to use a specific handler, False to use the internal handler.
Defaults to False.
"""
-
# Logger
self.logger: Logger
if not speHandler:
diff --git a/geos-processing/tests/test_SurfaceGeomechanics.py b/geos-processing/tests/test_SurfaceGeomechanics.py
index 7623e802e..dfdb030a6 100644
--- a/geos-processing/tests/test_SurfaceGeomechanics.py
+++ b/geos-processing/tests/test_SurfaceGeomechanics.py
@@ -14,7 +14,7 @@
from vtkmodules.vtkCommonCore import vtkPoints, vtkFloatArray
import vtkmodules.util.numpy_support as vnp
from geos.processing.post_processing.SurfaceGeomechanics import SurfaceGeomechanics
-
+from geos.utils.Errors import VTKError
@dataclass
class TriangulatedSurfaceTestCase:
From cdd923cd3e918608fd5e0bdde56f4cb8553904f9 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Fri, 31 Oct 2025 14:51:14 +0100
Subject: [PATCH 21/62] typing
---
geos-processing/tests/test_SurfaceGeomechanics.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/geos-processing/tests/test_SurfaceGeomechanics.py b/geos-processing/tests/test_SurfaceGeomechanics.py
index dfdb030a6..8ebda0e7f 100644
--- a/geos-processing/tests/test_SurfaceGeomechanics.py
+++ b/geos-processing/tests/test_SurfaceGeomechanics.py
@@ -14,7 +14,6 @@
from vtkmodules.vtkCommonCore import vtkPoints, vtkFloatArray
import vtkmodules.util.numpy_support as vnp
from geos.processing.post_processing.SurfaceGeomechanics import SurfaceGeomechanics
-from geos.utils.Errors import VTKError
@dataclass
class TriangulatedSurfaceTestCase:
From 24e4c5b9655b0f1b91ba5b09f60711c80013b3ae Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Fri, 31 Oct 2025 15:03:24 +0100
Subject: [PATCH 22/62] typing
---
geos-processing/tests/test_SurfaceGeomechanics.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/geos-processing/tests/test_SurfaceGeomechanics.py b/geos-processing/tests/test_SurfaceGeomechanics.py
index 8ebda0e7f..7623e802e 100644
--- a/geos-processing/tests/test_SurfaceGeomechanics.py
+++ b/geos-processing/tests/test_SurfaceGeomechanics.py
@@ -15,6 +15,7 @@
import vtkmodules.util.numpy_support as vnp
from geos.processing.post_processing.SurfaceGeomechanics import SurfaceGeomechanics
+
@dataclass
class TriangulatedSurfaceTestCase:
pointsCoords: npt.NDArray[ np.float64 ]
From 7e7eca119559adbbcf528000bbadf02fea83d7a9 Mon Sep 17 00:00:00 2001
From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com>
Date: Fri, 31 Oct 2025 15:53:19 +0100
Subject: [PATCH 23/62] .
---
geos-mesh/tests/conftest.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/geos-mesh/tests/conftest.py b/geos-mesh/tests/conftest.py
index 05fd52216..40f5dd46b 100644
--- a/geos-mesh/tests/conftest.py
+++ b/geos-mesh/tests/conftest.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Paloma Martinez
+# SPDX-FileContributor: Paloma Martinez, Romain Baville
# SPDX-License-Identifier: Apache 2.0
# ruff: noqa: E402 # disable Module level import not at top of file
import os
From dab09d01969ebd642ae8e2251052492b45390250 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Fri, 31 Oct 2025 16:47:50 +0100
Subject: [PATCH 24/62] Move GeosBlockMerge from geos-posp to geos-processing
---
docs/geos_posp_docs/filters.rst | 12 ------------
docs/geos_posp_docs/modules.rst | 2 --
docs/geos_processing_docs/post_processing.rst | 9 +++++++++
geos-posp/src/geos_posp/filters/__init__.py | 0
.../processing/post_processing}/GeosBlockMerge.py | 0
5 files changed, 9 insertions(+), 14 deletions(-)
delete mode 100644 docs/geos_posp_docs/filters.rst
delete mode 100644 geos-posp/src/geos_posp/filters/__init__.py
rename {geos-posp/src/geos_posp/filters => geos-processing/src/geos/processing/post_processing}/GeosBlockMerge.py (100%)
diff --git a/docs/geos_posp_docs/filters.rst b/docs/geos_posp_docs/filters.rst
deleted file mode 100644
index 7bd6a88d4..000000000
--- a/docs/geos_posp_docs/filters.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-vtk Filters
-===========
-
-This package defines vtk filters that allows to process Geos outputs.
-
-geos_posp.filters.GeosBlockMerge module
--------------------------------------------
-
-.. automodule:: geos_posp.filters.GeosBlockMerge
- :members:
- :undoc-members:
- :show-inheritance:
diff --git a/docs/geos_posp_docs/modules.rst b/docs/geos_posp_docs/modules.rst
index cf77644b1..a87e24ebb 100644
--- a/docs/geos_posp_docs/modules.rst
+++ b/docs/geos_posp_docs/modules.rst
@@ -4,6 +4,4 @@ Processing
.. toctree::
:maxdepth: 5
- filters
-
pyvistaTools
diff --git a/docs/geos_processing_docs/post_processing.rst b/docs/geos_processing_docs/post_processing.rst
index 58224d9fe..19b2906ae 100644
--- a/docs/geos_processing_docs/post_processing.rst
+++ b/docs/geos_processing_docs/post_processing.rst
@@ -44,3 +44,12 @@ geos.processing.post_processing.GeosBlockExtractor module
:members:
:undoc-members:
:show-inheritance:
+
+
+geos.processing.post_processing.GeosBlockMerge module
+-----------------------------------------------------
+
+.. automodule:: geos.processing.post_processing.GeosBlockMerge
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/geos-posp/src/geos_posp/filters/__init__.py b/geos-posp/src/geos_posp/filters/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/geos-posp/src/geos_posp/filters/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
similarity index 100%
rename from geos-posp/src/geos_posp/filters/GeosBlockMerge.py
rename to geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
From 7677e88c7bc6a12d20c40c2ea2b3f19c0612f7fd Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Fri, 31 Oct 2025 17:06:47 +0100
Subject: [PATCH 25/62] Remove PVPythonAlgorythmBase
---
.../post_processing/GeosBlockMerge.py | 56 ++-----------------
1 file changed, 5 insertions(+), 51 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index 4b76d9a24..e4816b433 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -69,65 +69,24 @@
"""
-class GeosBlockMerge( VTKPythonAlgorithmBase ):
+class GeosBlockMerge():
- def __init__( self: Self ) -> None:
+ def __init__( self: Self, inputMesh: vtkMultiBlockDataSet ) -> None:
"""VTK Filter that perform GEOS rank merge.
The filter returns a multiblock mesh composed of elementary blocks.
"""
- super().__init__( nInputPorts=1, nOutputPorts=1,
- outputType="vtkMultiBlockDataSet" ) # type: ignore[no-untyped-call]
- self.m_input: vtkMultiBlockDataSet
- self.m_output: vtkMultiBlockDataSet
+ self.m_input: vtkMultiBlockDataSet = inputMesh
+ self.m_output: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
self.m_convertFaultToSurface: bool = True
# set logger
self.m_logger: Logger = getLogger( "Geos Block Merge Filter" )
- 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(), "vtkMultiBlockDataSet" )
- return 1
-
- 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 RequestData(
- self: Self,
- request: vtkInformation,
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
+ def applyFilter( self: Self ) -> int:
"""Inherited from VTKPythonAlgorithmBase::RequestData.
Args:
@@ -139,11 +98,6 @@ def RequestData(
int: 1 if calculation successfully ended, 0 otherwise.
"""
try:
- self.m_input = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
-
- # initialize output objects -- TODO: separate it as soon as we are sure not to alter behavior
- self.m_output = self.GetOutputData( outInfoVec, 0 ) # type: ignore[no-untyped-call]
-
self.doMerge()
except ( ValueError, TypeError, RuntimeError ) as e:
self.m_logger.critical( "Geos block merge failed due to:" )
From 95072db3e606c2c4187054fbf8fbae9b3be5b587 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Mon, 3 Nov 2025 16:49:54 +0100
Subject: [PATCH 26/62] clean & refactor the code
---
.../post_processing/GeosBlockMerge.py | 315 ++++--------------
1 file changed, 72 insertions(+), 243 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index e4816b433..f20d815d8 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -1,36 +1,27 @@
# 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 geos.utils.GeosOutputsConstants import (
PHASE_SEP,
- FluidPrefixEnum,
PhaseTypeEnum,
PostProcessingOutputsEnum,
getRockSuffixRenaming,
)
from geos.utils.Logger import Logger, getLogger
from typing_extensions import Self
-from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase
-from vtkmodules.vtkCommonCore import (
- vtkInformation,
- vtkInformationVector,
-)
from vtkmodules.vtkCommonDataModel import (
vtkCompositeDataSet,
- vtkDataObjectTreeIterator,
vtkMultiBlockDataSet,
vtkPolyData,
vtkUnstructuredGrid,
)
-from vtkmodules.vtkFiltersCore import vtkArrayRename
from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter
from geos.mesh.utils.multiblockHelpers import getElementaryCompositeBlockIndexes
from geos.mesh.utils.arrayHelpers import getAttributeSet
-from geos.mesh.utils.arrayModifiers import createConstantAttribute, fillAllPartialAttributes
-from geos.mesh.utils.multiblockHelpers import extractBlock
+from geos.mesh.utils.arrayModifiers import createConstantAttribute, renameAttribute
from geos.mesh.utils.multiblockModifiers import mergeBlocks
from geos.mesh.utils.genericHelpers import (
computeNormals,
@@ -53,7 +44,7 @@
logger :Logger
input :vtkMultiBlockDataSet
- # instanciate the filter
+ # instantiate the filter
mergeBlockFilter :GeosBlockMerge = GeosBlockMerge()
# set the logger
mergeBlockFilter.SetLogger(logger)
@@ -77,35 +68,18 @@ def __init__( self: Self, inputMesh: vtkMultiBlockDataSet ) -> None:
The filter returns a multiblock mesh composed of elementary blocks.
"""
+ self.m_inputMesh: vtkMultiBlockDataSet = inputMesh
+ self.m_outputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- self.m_input: vtkMultiBlockDataSet = inputMesh
- self.m_output: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
-
- self.m_convertFaultToSurface: bool = True
+ self.m_convertFaultToSurface: bool = False
+ self.phaseNameDict: dict[ str, set[ str ] ] = {
+ PhaseTypeEnum.ROCK.type: {},
+ PhaseTypeEnum.FLUID.type: {},
+ }
# set logger
self.m_logger: Logger = getLogger( "Geos Block Merge Filter" )
- def applyFilter( self: Self ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestData.
-
- Args:
- request (vtkInformation): request
- inInfoVec (list[vtkInformationVector]): input objects
- outInfoVec (vtkInformationVector): output objects
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- try:
- self.doMerge()
- except ( ValueError, TypeError, RuntimeError ) as e:
- self.m_logger.critical( "Geos block merge failed due to:" )
- self.m_logger.critical( e, exc_info=True )
- return 0
- else:
- return 1
-
def SetLogger( self: Self, logger: Logger ) -> None:
"""Set the logger.
@@ -115,235 +89,90 @@ def SetLogger( self: Self, logger: Logger ) -> None:
self.m_logger = logger
def ConvertSurfaceMeshOn( self: Self ) -> None:
- """Activate surface conversion from vtkUnstructredGrid to vtkPolyData."""
+ """Activate surface conversion from vtkUnstructuredGrid to vtkPolyData."""
self.m_convertFaultToSurface = True
def ConvertSurfaceMeshOff( self: Self ) -> None:
- """Deactivate surface conversion from vtkUnstructredGrid to vtkPolyData."""
+ """Deactivate surface conversion from vtkUnstructuredGrid to vtkPolyData."""
self.m_convertFaultToSurface = False
- def doMerge( self: Self ) -> int:
- """Apply block merge.
-
- Returns:
- bool: True if block merge successfully ended, False otherwise.
- """
- self.mergeRankBlocks()
- if self.m_convertFaultToSurface:
- self.convertFaultsToSurfaces()
-
- self.m_output.ShallowCopy( self.m_outputMesh )
- return 1
-
- def mergeRankBlocks( self: Self ) -> bool:
- """Merge all elementary node that belong to a same parent node.
-
- Returns:
- bool: True if calculation successfully ended, False otherwise
- """
- # display phase names
+ def applyFilter( self: Self ) -> None:
+ """Merge all elementary node that belong to a same parent node."""
try:
- phaseClassification: dict[ str, PhaseTypeEnum ] = self.getPhases( True )
- if phaseClassification is not None:
- for phaseTypeRef in list( PhaseTypeEnum ):
- phases = [
- phaseName for phaseName, phaseType in phaseClassification.items() if phaseType is phaseTypeRef
- ]
- if len( phases ) > 0:
- self.m_logger.info( f"Identified {phaseTypeRef.type} phase(s) are: {phases}" )
- except AssertionError as e:
- self.m_logger.warning( "Phases were not identified due to:" )
- self.m_logger.warning( e )
-
- compositeBlockIndexesToMerge: dict[ str, int ] = ( getElementaryCompositeBlockIndexes( self.m_input ) )
- nbBlocks: int = len( compositeBlockIndexesToMerge )
- self.m_outputMesh = vtkMultiBlockDataSet()
- self.m_outputMesh.SetNumberOfBlocks( nbBlocks )
- for newIndex, ( blockName, blockIndex ) in enumerate( compositeBlockIndexesToMerge.items() ):
- # extract composite block
- blockToMerge1: vtkMultiBlockDataSet = extractBlock( self.m_input, blockIndex )
- assert blockToMerge1 is not None, "Extracted block to merge is null."
-
- # rename attributes
- blockToMerge2: vtkMultiBlockDataSet = self.renameAttributes( blockToMerge1, phaseClassification )
- assert blockToMerge2 is not None, "Attribute renaming failed."
-
- # merge all its children
- mergedBlock: vtkUnstructuredGrid = self.mergeChildBlocks( blockToMerge2 )
-
- # create index attribute keeping the index in intial mesh
- if not createConstantAttribute(
- mergedBlock,
- [
- blockIndex,
- ],
- PostProcessingOutputsEnum.BLOCK_INDEX.attributeName,
- (),
- False,
- ):
- self.m_logger.warning( "BlockIndex attribute was not created." )
-
- # set this composite block into the output
- self.m_outputMesh.SetBlock( newIndex, mergedBlock )
- self.m_outputMesh.GetMetaData( newIndex ).Set( vtkCompositeDataSet.NAME(), blockName )
-
- assert ( self.m_outputMesh.GetNumberOfBlocks() == nbBlocks ), "Final number of merged blocks is wrong."
- return True
+ # Display phase names
+ self.commutePhaseNames()
+ for phase, phaseNames in self.phaseNameDict.items():
+ if len( phaseNames ) > 0:
+ self.m_logger.info( f"Identified { phase } phase(s) are: { phaseNames }." )
+ else:
+ self.m_logger.info( f"No { phase } phase has been identified." )
+
+ # Merge all the composite blocks
+ compositeBlockIndexesToMerge: dict[ str, int ] = getElementaryCompositeBlockIndexes( self.m_inputMesh )
+ nbBlocks: int = len( compositeBlockIndexesToMerge )
+ self.m_outputMesh.SetNumberOfBlocks( nbBlocks )
+ for newIndex, ( blockName, blockIndex ) in enumerate( compositeBlockIndexesToMerge.items() ):
+ # Set the name of the merged block
+ self.m_outputMesh.GetMetaData( newIndex ).Set( vtkCompositeDataSet.NAME(), blockName )
+
+ # Merge blocks
+ blockToMerge: vtkMultiBlockDataSet = vtkMultiBlockDataSet.SafeDownCast( self.m_inputMesh.GetBlock( blockIndex ) )
+ volumeMesh: vtkUnstructuredGrid = mergeBlocks( blockToMerge, keepPartialAttributes=True, logger=self.m_logger )
+
+ # Create index attribute keeping the index in initial mesh
+ if not createConstantAttribute( volumeMesh, [ blockIndex ], PostProcessingOutputsEnum.BLOCK_INDEX.attributeName, onPoints=False, logger=self.m_logger ):
+ self.m_logger.warning( "BlockIndex attribute was not created." )
+
+ # Rename attributes
+ self.renameAttributes( volumeMesh )
+
+ # Convert the volume mesh to a surface mesh
+ if self.m_convertFaultToSurface:
+ surfaceMesh: vtkPolyData = self.convertBlockToSurface( volumeMesh )
+ assert surfaceMesh is not None, "Surface extraction from block failed."
+ surfaceMesh.ShallowCopy( computeNormals( surfaceMesh, logger=self.m_logger ) )
+ assert surfaceMesh is not None, "Normal calculation failed."
+ surfaceMesh.ShallowCopy( computeTangents( surfaceMesh, logger=self.m_logger ) )
+ assert surfaceMesh is not None, "Tangent calculation failed."
+ # Add the merged block to the output
+ self.m_outputMesh.SetBlock( newIndex, surfaceMesh )
+ else:
+ self.m_outputMesh.SetBlock( newIndex, volumeMesh )
+ except ( ValueError, TypeError, RuntimeError ) as e:
+ self.m_logger.critical( "Geos block merge failed due to:" )
+ self.m_logger.critical( e, exc_info=True )
+
+ return
def renameAttributes(
self: Self,
- mesh: vtkMultiBlockDataSet,
- phaseClassification: dict[ str, PhaseTypeEnum ],
- ) -> vtkMultiBlockDataSet:
+ mesh: vtkUnstructuredGrid,
+ ) -> None:
"""Rename attributes to harmonize throughout the mesh.
Args:
mesh (vtkMultiBlockDataSet): input mesh
phaseClassification (dict[str, PhaseTypeEnum]): phase classification
detected from attributes
-
- Returns:
- vtkMultiBlockDataSet: output mesh with renamed attributes
"""
- assert phaseClassification is not None, "Phases were not correctly identified."
-
- renameFilter: vtkArrayRename = vtkArrayRename()
- renameFilter.SetInputData( mesh )
- rockPhase: list[ str ] = [
- phaseName for phaseName, phaseType in phaseClassification.items() if phaseType is PhaseTypeEnum.ROCK
- ]
for attributeName in getAttributeSet( mesh, False ):
- for phaseName in rockPhase:
- if phaseName in attributeName:
- for suffix, newName in getRockSuffixRenaming().items():
- if suffix in attributeName:
- renameFilter.SetCellArrayName( attributeName, newName )
-
- renameFilter.Update()
- output: vtkMultiBlockDataSet = renameFilter.GetOutput()
- return output
-
- def getPhaseNames( self: Self, attributeSet: set[ str ] ) -> set[ str ]:
- """Get the names of the phases in the mesh from Point/Cell attributes.
+ for suffix, newName in getRockSuffixRenaming().items():
+ if suffix in attributeName:
+ renameAttribute( mesh, attributeName, newName, False )
- Args:
- attributeSet (set[str]): list of attributes where to find phases
-
- Returns:
- set[str]: the list of phase names that appear at least twice.
- """
- phaseNameDict: dict[ str, int ] = {}
- for name in attributeSet:
+ def commutePhaseNames( self: Self ) -> None:
+ """Get the names of the phases in the mesh from Cell attributes."""
+ for name in getAttributeSet( self.m_inputMesh, False ):
if PHASE_SEP in name:
- # use the last occurence of PHASE_SEP to split phase name from
- # property name
index = name.rindex( PHASE_SEP )
phaseName: str = name[ :index ]
- if phaseName in phaseNameDict:
- phaseNameDict[ phaseName ] += 1
- else:
- phaseNameDict[ phaseName ] = 1
-
- # remove names that appear only once
- return set( phaseNameDict.keys() )
-
- def getPhases( self: Self, onCells: bool = True ) -> dict[ str, PhaseTypeEnum ]:
- """Get the dictionnary of phases classified according to PhaseTypeEnum.
-
- Args:
- onCells (bool, optional): Attributes are on Cells (Default) or on
- Points.
+ suffixName: str = name[ index: ]
+ if suffixName in PhaseTypeEnum.ROCK.attributes:
+ self.phaseNameDict[ PhaseTypeEnum.ROCK.type ].add( phaseName )
+ elif suffixName in PhaseTypeEnum.FLUID.attributes:
+ self.phaseNameDict[ PhaseTypeEnum.FLUID.type ].add( phaseName )
- Defaults to True.
-
- Returns:
- dict[str, PhaseTypeEnum]: a dictionnary with phase names as keys and
- phase type as value.
- """
- attributeSet: set[ str ] = getAttributeSet( self.m_input, not onCells )
- assert len( attributeSet ) > 0, "Input object does not have any attribute."
-
- phaseClassification: dict[ str, PhaseTypeEnum ] = {}
- phaseNames: set[ str ] = self.getPhaseNames( attributeSet )
- for phaseName in phaseNames:
- # check for fluid phase names (most often the same names: fluid, water, or gas)
- if any( fluidPrefix.value.lower() in phaseName.lower() for fluidPrefix in list( FluidPrefixEnum ) ):
- phaseClassification[ phaseName ] = PhaseTypeEnum.FLUID
- continue
-
- for attributeName in attributeSet:
- if phaseName in attributeName:
- index = attributeName.index( phaseName ) + len( phaseName )
- suffix = attributeName[ index: ]
- for phaseType in list( PhaseTypeEnum ):
- if suffix in phaseType.attributes:
- if ( phaseName in phaseClassification ) and ( phaseType
- is not phaseClassification[ phaseName ] ):
- self.m_logger.warning( f"The phase {phaseName} may be misclassified " +
- "since the same name is used for both " +
- "{phaseType.type} and " +
- "{phaseClassification[phaseName].type} types" )
- phaseClassification[ phaseName ] = phaseType
-
- return phaseClassification
-
- def mergeChildBlocks( self: Self, compositeBlock: vtkMultiBlockDataSet ) -> vtkUnstructuredGrid:
- """Merge all children of the input composite block.
-
- Args:
- compositeBlock (vtkMultiBlockDataSet): composite block
-
- Returns:
- vtkUnstructuredGrid: merged block
- """
- # fill partial attributes in all children blocks
- if not fillAllPartialAttributes( compositeBlock ):
- self.m_logger.warning( "Some partial attributes may not have been propagated to the whole mesh." )
-
- # merge blocks
- mergedBlocks = mergeBlocks( compositeBlock )
- return mergedBlocks
-
- def convertFaultsToSurfaces( self: Self ) -> bool:
- """Convert blocks corresponding to faults to surface.
-
- Returns:
- bool: True if calculation succesfully ended, False otherwise
- """
- assert self.m_outputMesh is not None, "Output surface is null."
-
- transientMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- nbSurfaces: int = self.m_outputMesh.GetNumberOfBlocks()
- transientMesh.SetNumberOfBlocks( nbSurfaces )
-
- # initialize data object tree iterator
- iter: vtkDataObjectTreeIterator = vtkDataObjectTreeIterator()
- iter.SetDataSet( self.m_outputMesh )
- iter.VisitOnlyLeavesOn()
- iter.GoToFirstItem()
- surfaceIndex: int = 0
- while iter.GetCurrentDataObject() is not None:
- surfaceName: str = iter.GetCurrentMetaData().Get( vtkCompositeDataSet.NAME() )
- # convert block to surface
- surface0: vtkUnstructuredGrid = vtkUnstructuredGrid.SafeDownCast( iter.GetCurrentDataObject() )
- surface1: vtkPolyData = self.convertBlockToSurface( surface0 )
- assert surface1 is not None, "Surface extraction from block failed."
- # compute normals
- surface2: vtkPolyData = computeNormals( surface1 )
- assert surface2 is not None, "Normal calculation failed."
- # compute tangents
- surface3: vtkPolyData = computeTangents( surface2 )
- assert surface3 is not None, "Tangent calculation failed."
-
- # set surface to output multiBlockDataSet
- transientMesh.SetBlock( surfaceIndex, surface3 )
- transientMesh.GetMetaData( surfaceIndex ).Set( vtkCompositeDataSet.NAME(), surfaceName )
-
- iter.GoToNextItem()
- surfaceIndex += 1
-
- self.m_outputMesh.ShallowCopy( transientMesh )
- return True
+ return
def convertBlockToSurface( self: Self, block: vtkUnstructuredGrid ) -> vtkPolyData:
"""Convert vtkUnstructuredGrid to a surface vtkPolyData.
From c2ab8fb9ef4116bfd0ccc97fac7169581fd1357e Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Mon, 3 Nov 2025 16:56:46 +0100
Subject: [PATCH 27/62] Update import
---
geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py | 2 +-
.../src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py | 2 +-
.../geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py | 2 +-
geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
index ef97c6597..b495e747d 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
@@ -23,7 +23,7 @@
)
from geos.utils.Logger import ERROR, INFO, Logger, getLogger
from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
-from geos_posp.filters.GeosBlockMerge import GeosBlockMerge
+from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
from geos.mesh.utils.arrayModifiers import (
copyAttribute,
createCellCenterAttribute,
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py
index c9a27e53c..33ac4f29a 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py
@@ -24,7 +24,7 @@
)
from geos.utils.Logger import ERROR, INFO, Logger, getLogger
from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
-from geos_posp.filters.GeosBlockMerge import GeosBlockMerge
+from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
from geos.mesh.utils.arrayModifiers import (
copyAttribute,
createCellCenterAttribute,
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
index 74710a347..ec388e482 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
@@ -24,7 +24,7 @@
)
from geos.utils.Logger import ERROR, INFO, Logger, getLogger
from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
-from geos_posp.filters.GeosBlockMerge import GeosBlockMerge
+from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
from geos.mesh.utils.arrayModifiers import (
copyAttribute,
createCellCenterAttribute,
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py
index 4b7e43897..9ae83c7f6 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py
@@ -27,7 +27,7 @@
)
from geos.utils.Logger import ERROR, INFO, Logger, getLogger
from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
-from geos_posp.filters.GeosBlockMerge import GeosBlockMerge
+from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
from geos.mesh.utils.arrayModifiers import (
copyAttribute,
createCellCenterAttribute,
From c2df04e03aa2c85f03972c94680356d5df1f9543 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Tue, 4 Nov 2025 08:41:50 +0100
Subject: [PATCH 28/62] Fix density name for fluid phase
---
.../post_processing/GeosBlockMerge.py | 27 ++++++++++++++-----
.../pv/plugins/PVExtractMergeBlocksVolume.py | 11 +++-----
.../src/geos/utils/GeosOutputsConstants.py | 4 +--
3 files changed, 27 insertions(+), 15 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index f20d815d8..bdc34e3f6 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -6,6 +6,7 @@
from geos.utils.GeosOutputsConstants import (
PHASE_SEP,
PhaseTypeEnum,
+ FluidPrefixEnum,
PostProcessingOutputsEnum,
getRockSuffixRenaming,
)
@@ -19,7 +20,7 @@
)
from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter
-from geos.mesh.utils.multiblockHelpers import getElementaryCompositeBlockIndexes
+from geos.mesh.utils.multiblockHelpers import getElementaryCompositeBlockIndexes, extractBlock
from geos.mesh.utils.arrayHelpers import getAttributeSet
from geos.mesh.utils.arrayModifiers import createConstantAttribute, renameAttribute
from geos.mesh.utils.multiblockModifiers import mergeBlocks
@@ -73,8 +74,8 @@ def __init__( self: Self, inputMesh: vtkMultiBlockDataSet ) -> None:
self.m_convertFaultToSurface: bool = False
self.phaseNameDict: dict[ str, set[ str ] ] = {
- PhaseTypeEnum.ROCK.type: {},
- PhaseTypeEnum.FLUID.type: {},
+ PhaseTypeEnum.ROCK.type: set(),
+ PhaseTypeEnum.FLUID.type: set(),
}
# set logger
@@ -96,6 +97,10 @@ def ConvertSurfaceMeshOff( self: Self ) -> None:
"""Deactivate surface conversion from vtkUnstructuredGrid to vtkPolyData."""
self.m_convertFaultToSurface = False
+ def getOutput ( self: Self ) -> vtkMultiBlockDataSet:
+ """Get the mesh with the composite blocks merged."""
+ return self.m_outputMesh
+
def applyFilter( self: Self ) -> None:
"""Merge all elementary node that belong to a same parent node."""
try:
@@ -116,7 +121,7 @@ def applyFilter( self: Self ) -> None:
self.m_outputMesh.GetMetaData( newIndex ).Set( vtkCompositeDataSet.NAME(), blockName )
# Merge blocks
- blockToMerge: vtkMultiBlockDataSet = vtkMultiBlockDataSet.SafeDownCast( self.m_inputMesh.GetBlock( blockIndex ) )
+ blockToMerge: vtkMultiBlockDataSet = extractBlock( self.m_inputMesh, blockIndex )
volumeMesh: vtkUnstructuredGrid = mergeBlocks( blockToMerge, keepPartialAttributes=True, logger=self.m_logger )
# Create index attribute keeping the index in initial mesh
@@ -158,7 +163,12 @@ def renameAttributes(
for attributeName in getAttributeSet( mesh, False ):
for suffix, newName in getRockSuffixRenaming().items():
if suffix in attributeName:
- renameAttribute( mesh, attributeName, newName, False )
+ if suffix == "_density":
+ for phaseName in self.phaseNameDict[ PhaseTypeEnum.ROCK.type ]:
+ if phaseName in attributeName:
+ renameAttribute( mesh, attributeName, newName, False )
+ elif suffix != "_density":
+ renameAttribute( mesh, attributeName, newName, False )
def commutePhaseNames( self: Self ) -> None:
"""Get the names of the phases in the mesh from Cell attributes."""
@@ -167,7 +177,12 @@ def commutePhaseNames( self: Self ) -> None:
index = name.rindex( PHASE_SEP )
phaseName: str = name[ :index ]
suffixName: str = name[ index: ]
- if suffixName in PhaseTypeEnum.ROCK.attributes:
+ if suffixName == "_density":
+ if any( phaseName in fluidPrefix.value for fluidPrefix in list( FluidPrefixEnum ) ):
+ self.phaseNameDict[ PhaseTypeEnum.FLUID.type ].add( phaseName )
+ else:
+ self.phaseNameDict[ PhaseTypeEnum.ROCK.type ].add( phaseName )
+ elif suffixName in PhaseTypeEnum.ROCK.attributes:
self.phaseNameDict[ PhaseTypeEnum.ROCK.type ].add( phaseName )
elif suffixName in PhaseTypeEnum.FLUID.attributes:
self.phaseNameDict[ PhaseTypeEnum.FLUID.type ].add( phaseName )
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
index b495e747d..c646f5f03 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
@@ -274,7 +274,7 @@ def doExtractAndMerge( self: Self, input: vtkMultiBlockDataSet, output: vtkMulti
output (vtkMultiBlockDataSet): output volume mesh
Returns:
- bool: True if extraction and merge successfully eneded, False otherwise
+ bool: True if extraction and merge successfully ended, False otherwise
"""
# extract blocks
blockExtractor: GeosBlockExtractor = GeosBlockExtractor( input )
@@ -303,14 +303,11 @@ def mergeBlocksFilter( self: Self,
Returns:
vtkMultiBlockDataSet: Multiblock mesh composed of internal merged blocks.
"""
- mergeBlockFilter: GeosBlockMerge = GeosBlockMerge()
+ mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( input )
mergeBlockFilter.SetLogger( self.m_logger )
- mergeBlockFilter.SetInputDataObject( input )
if convertSurfaces:
mergeBlockFilter.ConvertSurfaceMeshOn()
- else:
- mergeBlockFilter.ConvertSurfaceMeshOff()
- mergeBlockFilter.Update()
- mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.GetOutputDataObject( 0 )
+ mergeBlockFilter.applyFilter()
+ mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
assert mergedBlocks is not None, "Final merged MultiBlockDataSet is null."
return mergedBlocks
diff --git a/geos-utils/src/geos/utils/GeosOutputsConstants.py b/geos-utils/src/geos/utils/GeosOutputsConstants.py
index a2fc72cb1..52d50a3f8 100644
--- a/geos-utils/src/geos/utils/GeosOutputsConstants.py
+++ b/geos-utils/src/geos/utils/GeosOutputsConstants.py
@@ -102,9 +102,9 @@ class GeosMeshSuffixEnum( Enum ):
BIOT_COEFFICIENT_SUFFIX = "_biotCoefficient"
# fluid attributes suffix
- PHASE_DENSITY_SUFFIX = "_phaseDensity"
+ PHASE_DENSITY_SUFFIX = "_density"
PHASE_MASS_DENSITY_SUFFIX = "_phaseMassDensity"
- PHASE_VISCOSITY_SUFFIX = "_phaseViscosity"
+ PHASE_VISCOSITY_SUFFIX = "_viscosity"
PHASE_FRACTION_SUFFIX = "_phaseFraction"
# surface attribute transfer suffix
From eabc05805b78a3d38c99e3b09d2c6b5ed391f957 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Tue, 4 Nov 2025 15:28:10 +0100
Subject: [PATCH 29/62] harmonized the code (logger, varriable name ...)
---
.../post_processing/GeosBlockMerge.py | 126 +++++++++++-------
1 file changed, 76 insertions(+), 50 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index bdc34e3f6..af4c2f10b 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.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
-
+import logging
from geos.utils.GeosOutputsConstants import (
PHASE_SEP,
PhaseTypeEnum,
@@ -17,6 +17,8 @@
vtkMultiBlockDataSet,
vtkPolyData,
vtkUnstructuredGrid,
+ vtkCellTypes,
+ vtkTriangle,
)
from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter
@@ -60,92 +62,111 @@
output :vtkMultiBlockDataSet = mergeBlockFilter.GetOutputDataObject(0)
"""
+loggerTitle: str = "GEOS Block Merge"
+
class GeosBlockMerge():
- def __init__( self: Self, inputMesh: vtkMultiBlockDataSet ) -> None:
+ def __init__(
+ self: Self,
+ inputMesh: vtkMultiBlockDataSet,
+ convertFaultToSurface: bool = False,
+ speHandler: bool = False,
+ ) -> None:
"""VTK Filter that perform GEOS rank merge.
- The filter returns a multiblock mesh composed of elementary blocks.
+ Args:
+ inputMesh (vtkMultiBlockDataSet): The mesh with the blocks to merge.
+ convertFaultToSurface (bool, optional): True if the merged block need to be convert to vtp, False otherwise.
+ Defaults to False.
+ speHandler (bool, optional): True to use a specific handler, False to use the internal handler.
+ Defaults to False.
"""
- self.m_inputMesh: vtkMultiBlockDataSet = inputMesh
- self.m_outputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
+ self.inputMesh: vtkMultiBlockDataSet = inputMesh
+ self.convertFaultToSurface: bool = convertFaultToSurface
- self.m_convertFaultToSurface: bool = False
+ self.outputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
self.phaseNameDict: dict[ str, set[ str ] ] = {
PhaseTypeEnum.ROCK.type: set(),
PhaseTypeEnum.FLUID.type: set(),
}
- # set logger
- self.m_logger: Logger = getLogger( "Geos Block Merge Filter" )
+ # Logger.
+ self.logger: Logger
+ if not speHandler:
+ self.logger = getLogger( loggerTitle, True )
+ else:
+ self.logger = logging.getLogger( loggerTitle )
+ self.logger.setLevel( logging.INFO )
+
+ 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
-
- def ConvertSurfaceMeshOn( self: Self ) -> None:
- """Activate surface conversion from vtkUnstructuredGrid to vtkPolyData."""
- self.m_convertFaultToSurface = True
-
- def ConvertSurfaceMeshOff( self: Self ) -> None:
- """Deactivate surface conversion from vtkUnstructuredGrid to vtkPolyData."""
- self.m_convertFaultToSurface = False
+ if not self.logger.hasHandlers():
+ self.logger.addHandler( handler )
+ else:
+ self.logger.warning(
+ "The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization."
+ )
def getOutput ( self: Self ) -> vtkMultiBlockDataSet:
"""Get the mesh with the composite blocks merged."""
- return self.m_outputMesh
+ return self.outputMesh
def applyFilter( self: Self ) -> None:
"""Merge all elementary node that belong to a same parent node."""
+ self.logger.info( f"Apply filter { self.logger.name }." )
+
try:
# Display phase names
self.commutePhaseNames()
for phase, phaseNames in self.phaseNameDict.items():
if len( phaseNames ) > 0:
- self.m_logger.info( f"Identified { phase } phase(s) are: { phaseNames }." )
+ self.logger.info( f"Identified { phase } phase(s) are: { phaseNames }." )
else:
- self.m_logger.info( f"No { phase } phase has been identified." )
+ self.logger.info( f"No { phase } phase has been identified." )
- # Merge all the composite blocks
- compositeBlockIndexesToMerge: dict[ str, int ] = getElementaryCompositeBlockIndexes( self.m_inputMesh )
+ # Parse all the composite blocks
+ compositeBlockIndexesToMerge: dict[ str, int ] = getElementaryCompositeBlockIndexes( self.inputMesh )
nbBlocks: int = len( compositeBlockIndexesToMerge )
- self.m_outputMesh.SetNumberOfBlocks( nbBlocks )
+ self.outputMesh.SetNumberOfBlocks( nbBlocks )
for newIndex, ( blockName, blockIndex ) in enumerate( compositeBlockIndexesToMerge.items() ):
- # Set the name of the merged block
- self.m_outputMesh.GetMetaData( newIndex ).Set( vtkCompositeDataSet.NAME(), blockName )
+ # Set the name of the composite block
+ self.outputMesh.GetMetaData( newIndex ).Set( vtkCompositeDataSet.NAME(), blockName )
# Merge blocks
- blockToMerge: vtkMultiBlockDataSet = extractBlock( self.m_inputMesh, blockIndex )
- volumeMesh: vtkUnstructuredGrid = mergeBlocks( blockToMerge, keepPartialAttributes=True, logger=self.m_logger )
+ blockToMerge: vtkMultiBlockDataSet = extractBlock( self.inputMesh, blockIndex )
+ volumeMesh: vtkUnstructuredGrid = mergeBlocks( blockToMerge, keepPartialAttributes=True, logger=self.logger )
# Create index attribute keeping the index in initial mesh
- if not createConstantAttribute( volumeMesh, [ blockIndex ], PostProcessingOutputsEnum.BLOCK_INDEX.attributeName, onPoints=False, logger=self.m_logger ):
- self.m_logger.warning( "BlockIndex attribute was not created." )
+ if not createConstantAttribute( volumeMesh, [ blockIndex ], PostProcessingOutputsEnum.BLOCK_INDEX.attributeName, onPoints=False, logger=self.logger ):
+ self.logger.warning( "BlockIndex attribute was not created." )
# Rename attributes
self.renameAttributes( volumeMesh )
# Convert the volume mesh to a surface mesh
- if self.m_convertFaultToSurface:
+ if self.convertFaultToSurface:
surfaceMesh: vtkPolyData = self.convertBlockToSurface( volumeMesh )
assert surfaceMesh is not None, "Surface extraction from block failed."
- surfaceMesh.ShallowCopy( computeNormals( surfaceMesh, logger=self.m_logger ) )
+ surfaceMesh.ShallowCopy( computeNormals( surfaceMesh, logger=self.logger ) )
assert surfaceMesh is not None, "Normal calculation failed."
- surfaceMesh.ShallowCopy( computeTangents( surfaceMesh, logger=self.m_logger ) )
+ surfaceMesh.ShallowCopy( computeTangents( surfaceMesh, logger=self.logger ) )
assert surfaceMesh is not None, "Tangent calculation failed."
- # Add the merged block to the output
- self.m_outputMesh.SetBlock( newIndex, surfaceMesh )
+ # Add the merged block to the output mesh
+ self.outputMesh.SetBlock( newIndex, surfaceMesh )
else:
- self.m_outputMesh.SetBlock( newIndex, volumeMesh )
+ self.outputMesh.SetBlock( newIndex, volumeMesh )
+
+ self.logger.info( "The filter succeeded." )
except ( ValueError, TypeError, RuntimeError ) as e:
- self.m_logger.critical( "Geos block merge failed due to:" )
- self.m_logger.critical( e, exc_info=True )
+ self.logger.critical( f"The filter failed.\n{ e }" )
return
@@ -153,31 +174,33 @@ def renameAttributes(
self: Self,
mesh: vtkUnstructuredGrid,
) -> None:
- """Rename attributes to harmonize throughout the mesh.
+ """Rename attributes to harmonize GEOS output, see more geos.utils.OutputsConstants.py.
Args:
- mesh (vtkMultiBlockDataSet): input mesh
- phaseClassification (dict[str, PhaseTypeEnum]): phase classification
- detected from attributes
+ mesh (vtkUnstructuredGrid): The mesh with the attribute to rename.
"""
+ # All the attributes to rename are on cells
for attributeName in getAttributeSet( mesh, False ):
for suffix, newName in getRockSuffixRenaming().items():
if suffix in attributeName:
+ # Fluid and Rock density attribute have the same suffix, only the rock density need to be renamed
if suffix == "_density":
for phaseName in self.phaseNameDict[ PhaseTypeEnum.ROCK.type ]:
if phaseName in attributeName:
renameAttribute( mesh, attributeName, newName, False )
- elif suffix != "_density":
+ else:
renameAttribute( mesh, attributeName, newName, False )
def commutePhaseNames( self: Self ) -> None:
"""Get the names of the phases in the mesh from Cell attributes."""
- for name in getAttributeSet( self.m_inputMesh, False ):
+ # All the phase attributes are on cells
+ for name in getAttributeSet( self.inputMesh, False ):
if PHASE_SEP in name:
- index = name.rindex( PHASE_SEP )
- phaseName: str = name[ :index ]
- suffixName: str = name[ index: ]
- if suffixName == "_density":
+ phaseName: str
+ suffixName: str
+ phaseName, suffixName = name.split( PHASE_SEP )
+ # Fluid and Rock density attribute have the same suffix, common fluid name are used to separated the two phases
+ if suffixName == "density":
if any( phaseName in fluidPrefix.value for fluidPrefix in list( FluidPrefixEnum ) ):
self.phaseNameDict[ PhaseTypeEnum.FLUID.type ].add( phaseName )
else:
@@ -203,6 +226,9 @@ def convertBlockToSurface( self: Self, block: vtkUnstructuredGrid ) -> vtkPolyDa
Returns:
vtkPolyData: extracted surface
"""
+ cellTypes: list[ vtkCellTypes ] = block.GetDistinctCellTypesArray()
+ assert [ vtkTriangle ] == cellTypes, "The surface mesh must be a triangulated surface only."
+
extractSurfaceFilter: vtkDataSetSurfaceFilter = vtkDataSetSurfaceFilter()
extractSurfaceFilter.SetInputData( block )
# fast mode should be used for rendering only
From ab7bfcbd05d9300eb6740ce4ef626d47064bf017 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Tue, 4 Nov 2025 15:52:30 +0100
Subject: [PATCH 30/62] Clean the doc
---
.../post_processing/GeosBlockMerge.py | 102 +++++++++---------
1 file changed, 51 insertions(+), 51 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index af4c2f10b..f3203b8de 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -3,63 +3,54 @@
# SPDX-FileContributor: Martin Lemay, Romain Baville
# ruff: noqa: E402 # disable Module level import not at top of file
import logging
-from geos.utils.GeosOutputsConstants import (
- PHASE_SEP,
- PhaseTypeEnum,
- FluidPrefixEnum,
- PostProcessingOutputsEnum,
- getRockSuffixRenaming,
-)
-from geos.utils.Logger import Logger, getLogger
from typing_extensions import Self
-from vtkmodules.vtkCommonDataModel import (
- vtkCompositeDataSet,
- vtkMultiBlockDataSet,
- vtkPolyData,
- vtkUnstructuredGrid,
- vtkCellTypes,
- vtkTriangle,
-)
+
+from vtkmodules.vtkCommonDataModel import ( vtkCompositeDataSet, vtkMultiBlockDataSet, vtkPolyData, vtkUnstructuredGrid,
+ vtkCellTypes, vtkTriangle )
from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter
-from geos.mesh.utils.multiblockHelpers import getElementaryCompositeBlockIndexes, extractBlock
+from geos.utils.Logger import ( Logger, getLogger )
+from geos.utils.GeosOutputsConstants import ( PHASE_SEP, PhaseTypeEnum, FluidPrefixEnum, PostProcessingOutputsEnum,
+ getRockSuffixRenaming )
from geos.mesh.utils.arrayHelpers import getAttributeSet
-from geos.mesh.utils.arrayModifiers import createConstantAttribute, renameAttribute
+from geos.mesh.utils.arrayModifiers import ( createConstantAttribute, renameAttribute )
from geos.mesh.utils.multiblockModifiers import mergeBlocks
-from geos.mesh.utils.genericHelpers import (
- computeNormals,
- computeTangents,
-)
+from geos.mesh.utils.multiblockHelpers import ( getElementaryCompositeBlockIndexes, extractBlock )
+from geos.mesh.utils.genericHelpers import ( computeNormals, computeTangents )
__doc__ = """
-GeosBlockMerge module is a vtk filter that allows to merge Geos ranks, rename
-stress and porosity attributes, and identify fluids and rock phases.
+GeosBlockMerge is a vtk filter that allows to merge for a GEOS domain the ranks per region, identify "Fluids" and "Rock" phases and rename "Rock" attributes.
Filter input and output types are vtkMultiBlockDataSet.
+.. Important::
+ This filter deals with the domain mesh of GEOS. This domain needs to be extracted before.
+ See geos-processing/src/geos/processing/post_processing/GeosBlockExtractor.py to see the type of input requires by this filter.
+
To use the filter:
.. code-block:: python
- from filters.GeosBlockMerge import GeosBlockMerge
-
- # filter inputs
- logger :Logger
- input :vtkMultiBlockDataSet
-
- # instantiate the filter
- mergeBlockFilter :GeosBlockMerge = GeosBlockMerge()
- # set the logger
- mergeBlockFilter.SetLogger(logger)
- # set input data object
- mergeBlockFilter.SetInputDataObject(input)
- # ConvertSurfaceMeshOff or ConvertSurfaceMeshOn to (de)activate the conversion
- # of vtkUnstructuredGrid to surface withNormals and Tangents calculation.
- mergeBlockFilter.ConvertSurfaceMeshOff()
- # do calculations
- mergeBlockFilter.Update()
- # get output object
- output :vtkMultiBlockDataSet = mergeBlockFilter.GetOutputDataObject(0)
+ from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
+
+ # Filter inputs.
+ inputMesh: vtkMultiBlockDataSet
+ # Optional inputs.
+ convertFaultToSurface: bool # Defaults to False
+ speHandler: bool # Defaults to False
+
+ # Instantiate the filter
+ mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( inputMesh, convertFaultToSurface, speHandler )
+
+ # Set the handler of yours (only if speHandler is True).
+ yourHandler: logging.Handler
+ mergeBlockFilter.setLoggerHandler( yourHandler )
+
+ # Do calculations
+ mergeBlockFilter.applyFilter()
+
+ # Get the multiBlockDataSet with one dataSet per region
+ outputMesh: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
"""
loggerTitle: str = "GEOS Block Merge"
@@ -73,7 +64,12 @@ def __init__(
convertFaultToSurface: bool = False,
speHandler: bool = False,
) -> None:
- """VTK Filter that perform GEOS rank merge.
+ """VTK Filter that merge ranks of GEOS output mesh.
+
+ for all the composite blocks of the input mesh:
+ - Ranks are merged
+ - "Rock" attributes are renamed
+ - Volume mesh are convert to surface if needed
Args:
inputMesh (vtkMultiBlockDataSet): The mesh with the blocks to merge.
@@ -115,12 +111,12 @@ 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 getOutput ( self: Self ) -> vtkMultiBlockDataSet:
+ def getOutput( self: Self ) -> vtkMultiBlockDataSet:
"""Get the mesh with the composite blocks merged."""
return self.outputMesh
def applyFilter( self: Self ) -> None:
- """Merge all elementary node that belong to a same parent node."""
+ """Apply the filter on the mesh."""
self.logger.info( f"Apply filter { self.logger.name }." )
try:
@@ -142,10 +138,15 @@ def applyFilter( self: Self ) -> None:
# Merge blocks
blockToMerge: vtkMultiBlockDataSet = extractBlock( self.inputMesh, blockIndex )
- volumeMesh: vtkUnstructuredGrid = mergeBlocks( blockToMerge, keepPartialAttributes=True, logger=self.logger )
+ volumeMesh: vtkUnstructuredGrid = mergeBlocks( blockToMerge,
+ keepPartialAttributes=True,
+ logger=self.logger )
# Create index attribute keeping the index in initial mesh
- if not createConstantAttribute( volumeMesh, [ blockIndex ], PostProcessingOutputsEnum.BLOCK_INDEX.attributeName, onPoints=False, logger=self.logger ):
+ if not createConstantAttribute( volumeMesh, [ blockIndex ],
+ PostProcessingOutputsEnum.BLOCK_INDEX.attributeName,
+ onPoints=False,
+ logger=self.logger ):
self.logger.warning( "BlockIndex attribute was not created." )
# Rename attributes
@@ -159,13 +160,13 @@ def applyFilter( self: Self ) -> None:
assert surfaceMesh is not None, "Normal calculation failed."
surfaceMesh.ShallowCopy( computeTangents( surfaceMesh, logger=self.logger ) )
assert surfaceMesh is not None, "Tangent calculation failed."
- # Add the merged block to the output mesh
+ # Add the merged block to the output mesh
self.outputMesh.SetBlock( newIndex, surfaceMesh )
else:
self.outputMesh.SetBlock( newIndex, volumeMesh )
self.logger.info( "The filter succeeded." )
- except ( ValueError, TypeError, RuntimeError ) as e:
+ except ( ValueError, TypeError, RuntimeError, AssertionError ) as e:
self.logger.critical( f"The filter failed.\n{ e }" )
return
@@ -219,7 +220,6 @@ def convertBlockToSurface( self: Self, block: vtkUnstructuredGrid ) -> vtkPolyDa
.. TODO:: need to convert quadrangular to triangulated surfaces first
-
Args:
block (vtkUnstructuredGrid): block from which to extract the surface
From 789cd31e0a137a647b37b551bb7a497af6d3340e Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Tue, 4 Nov 2025 15:58:24 +0100
Subject: [PATCH 31/62] Update GeosBlockMerge calling
---
.../pv/plugins/PVExtractMergeBlocksVolume.py | 10 ++++++----
.../PVExtractMergeBlocksVolumeSurface.py | 17 ++++++++---------
.../PVExtractMergeBlocksVolumeSurfaceWell.py | 17 ++++++++---------
.../plugins/PVExtractMergeBlocksVolumeWell.py | 17 ++++++++---------
4 files changed, 30 insertions(+), 31 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
index c646f5f03..382f8bc87 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
@@ -32,6 +32,9 @@
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
__doc__ = """
PVExtractMergeBlocksVolume is a Paraview plugin that allows to merge ranks
@@ -303,10 +306,9 @@ def mergeBlocksFilter( self: Self,
Returns:
vtkMultiBlockDataSet: Multiblock mesh composed of internal merged blocks.
"""
- mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( input )
- mergeBlockFilter.SetLogger( self.m_logger )
- if convertSurfaces:
- mergeBlockFilter.ConvertSurfaceMeshOn()
+ mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( input, convertSurfaces, True )
+ if not mergeBlockFilter.logger.hasHandlers():
+ mergeBlockFilter.setLoggerHandler( VTKHandler() )
mergeBlockFilter.applyFilter()
mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
assert mergedBlocks is not None, "Final merged MultiBlockDataSet is null."
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py
index 33ac4f29a..a670df2b5 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py
@@ -33,6 +33,9 @@
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
__doc__ = """
PVExtractMergeBlocksVolumeSurface is a Paraview plugin that allows to merge
@@ -325,14 +328,10 @@ def mergeBlocksFilter( self: Self,
Returns:
vtkMultiBlockDataSet: Multiblock mesh composed of internal merged blocks.
"""
- mergeBlockFilter = GeosBlockMerge()
- mergeBlockFilter.SetLogger( self.m_logger )
- mergeBlockFilter.SetInputDataObject( input )
- if convertSurfaces:
- mergeBlockFilter.ConvertSurfaceMeshOn()
- else:
- mergeBlockFilter.ConvertSurfaceMeshOff()
- mergeBlockFilter.Update()
- mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.GetOutputDataObject( 0 )
+ mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( input, convertSurfaces, True )
+ if not mergeBlockFilter.logger.hasHandlers():
+ mergeBlockFilter.setLoggerHandler( VTKHandler() )
+ mergeBlockFilter.applyFilter()
+ mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
assert mergedBlocks is not None, "Final merged MultiBlockDataSet is null."
return mergedBlocks
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
index ec388e482..d3eff7aff 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
@@ -33,6 +33,9 @@
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
__doc__ = """
PVExtractMergeBlocksVolumeSurfaceWell is a Paraview plugin that allows to merge
@@ -353,14 +356,10 @@ def mergeBlocksFilter( self: Self,
Returns:
vtkMultiBlockDataSet: Multiblock mesh composed of internal merged blocks.
"""
- mergeBlockFilter = GeosBlockMerge()
- mergeBlockFilter.SetLogger( self.m_logger )
- mergeBlockFilter.SetInputDataObject( input )
- if convertSurfaces:
- mergeBlockFilter.ConvertSurfaceMeshOn()
- else:
- mergeBlockFilter.ConvertSurfaceMeshOff()
- mergeBlockFilter.Update()
- mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.GetOutputDataObject( 0 )
+ mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( input, convertSurfaces, True )
+ if not mergeBlockFilter.logger.hasHandlers():
+ mergeBlockFilter.setLoggerHandler( VTKHandler() )
+ mergeBlockFilter.applyFilter()
+ mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
assert mergedBlocks is not None, "Final merged MultiBlockDataSet is null."
return mergedBlocks
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py
index 9ae83c7f6..d1da166b9 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py
@@ -10,6 +10,9 @@
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 typing_extensions import Self
from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector
from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet
@@ -334,14 +337,10 @@ def mergeBlocksFilter( self: Self,
Returns:
vtkMultiBlockDataSet: Multiblock mesh composed of internal merged blocks.
"""
- mergeBlockFilter = GeosBlockMerge()
- mergeBlockFilter.SetLogger( self.m_logger )
- mergeBlockFilter.SetInputDataObject( input )
- if convertSurfaces:
- mergeBlockFilter.ConvertSurfaceMeshOn()
- else:
- mergeBlockFilter.ConvertSurfaceMeshOff()
- mergeBlockFilter.Update()
- mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.GetOutputDataObject( 0 )
+ mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( input, convertSurfaces, True )
+ if not mergeBlockFilter.logger.hasHandlers():
+ mergeBlockFilter.setLoggerHandler( VTKHandler() )
+ mergeBlockFilter.applyFilter()
+ mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
assert mergedBlocks is not None, "Final merged MultiBlockDataSet is null."
return mergedBlocks
From 8f08be87d27ee6d5a2fd09bbd67bed7761d58114 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Tue, 4 Nov 2025 16:14:30 +0100
Subject: [PATCH 32/62] fix computePhaseNames
---
.../geos/processing/post_processing/GeosBlockMerge.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index f3203b8de..cf285e295 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -121,7 +121,7 @@ def applyFilter( self: Self ) -> None:
try:
# Display phase names
- self.commutePhaseNames()
+ self.computePhaseNames()
for phase, phaseNames in self.phaseNameDict.items():
if len( phaseNames ) > 0:
self.logger.info( f"Identified { phase } phase(s) are: { phaseNames }." )
@@ -192,7 +192,7 @@ def renameAttributes(
else:
renameAttribute( mesh, attributeName, newName, False )
- def commutePhaseNames( self: Self ) -> None:
+ def computePhaseNames( self: Self ) -> None:
"""Get the names of the phases in the mesh from Cell attributes."""
# All the phase attributes are on cells
for name in getAttributeSet( self.inputMesh, False ):
@@ -201,14 +201,14 @@ def commutePhaseNames( self: Self ) -> None:
suffixName: str
phaseName, suffixName = name.split( PHASE_SEP )
# Fluid and Rock density attribute have the same suffix, common fluid name are used to separated the two phases
- if suffixName == "density":
+ if f"{ PHASE_SEP }{ suffixName }" == "_density":
if any( phaseName in fluidPrefix.value for fluidPrefix in list( FluidPrefixEnum ) ):
self.phaseNameDict[ PhaseTypeEnum.FLUID.type ].add( phaseName )
else:
self.phaseNameDict[ PhaseTypeEnum.ROCK.type ].add( phaseName )
- elif suffixName in PhaseTypeEnum.ROCK.attributes:
+ elif f"{ PHASE_SEP }{ suffixName }" in PhaseTypeEnum.ROCK.attributes:
self.phaseNameDict[ PhaseTypeEnum.ROCK.type ].add( phaseName )
- elif suffixName in PhaseTypeEnum.FLUID.attributes:
+ elif f"{ PHASE_SEP }{ suffixName }" in PhaseTypeEnum.FLUID.attributes:
self.phaseNameDict[ PhaseTypeEnum.FLUID.type ].add( phaseName )
return
From 13ea5b5a0256d8015bc710a4e49040f5a0e675e2 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Tue, 4 Nov 2025 18:40:59 +0100
Subject: [PATCH 33/62] fix convertBlockToSurface
---
.../src/geos/processing/post_processing/GeosBlockMerge.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index cf285e295..53588c6b5 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -226,9 +226,6 @@ def convertBlockToSurface( self: Self, block: vtkUnstructuredGrid ) -> vtkPolyDa
Returns:
vtkPolyData: extracted surface
"""
- cellTypes: list[ vtkCellTypes ] = block.GetDistinctCellTypesArray()
- assert [ vtkTriangle ] == cellTypes, "The surface mesh must be a triangulated surface only."
-
extractSurfaceFilter: vtkDataSetSurfaceFilter = vtkDataSetSurfaceFilter()
extractSurfaceFilter.SetInputData( block )
# fast mode should be used for rendering only
From 77556393f4c3c00e63276e7528f73a417cb1d55e Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Tue, 4 Nov 2025 18:43:31 +0100
Subject: [PATCH 34/62] fix ci
---
.../src/geos/processing/post_processing/GeosBlockMerge.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index 53588c6b5..b1188ddf5 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -5,8 +5,8 @@
import logging
from typing_extensions import Self
-from vtkmodules.vtkCommonDataModel import ( vtkCompositeDataSet, vtkMultiBlockDataSet, vtkPolyData, vtkUnstructuredGrid,
- vtkCellTypes, vtkTriangle )
+from vtkmodules.vtkCommonDataModel import ( vtkCompositeDataSet, vtkMultiBlockDataSet, vtkPolyData,
+ vtkUnstructuredGrid )
from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter
from geos.utils.Logger import ( Logger, getLogger )
From e23251bc3a4300e04e7424543d68d122304a0bce Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Wed, 5 Nov 2025 19:24:14 +0100
Subject: [PATCH 35/62] Refactor plugin to allways extract and merge all the
possible blocks
---
.../PVExtractMergeBlocksVolumeSurfaceWell.py | 208 +++++++-----------
1 file changed, 83 insertions(+), 125 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
index d3eff7aff..45553b87d 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
@@ -6,6 +6,7 @@
from pathlib import Path
import numpy as np
+import logging
import numpy.typing as npt
from typing_extensions import Self
from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector
@@ -20,15 +21,17 @@
from geos.utils.GeosOutputsConstants import (
GeosMeshOutputsEnum,
+ GeosDomainNameEnum,
getAttributeToTransferFromInitialTime,
)
-from geos.utils.Logger import ERROR, INFO, Logger, getLogger
from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
+from geos.mesh.utils.arrayHelpers import getAttributeSet
from geos.mesh.utils.arrayModifiers import (
copyAttribute,
createCellCenterAttribute,
)
+from geos.mesh.utils.multiblockHelpers import getBlockNames
from geos.pv.utils.paraviewTreatments import getTimeStepIndex
from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
@@ -38,7 +41,7 @@
) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py
__doc__ = """
-PVExtractMergeBlocksVolumeSurfaceWell is a Paraview plugin that allows to merge
+PVGeosBlockExtractAndMerge is a Paraview plugin that allows to merge
ranks of a Geos output objects containing a volumic mesh, surfaces, and wells.
Input and output types are vtkMultiBlockDataSet.
@@ -54,26 +57,32 @@
To use it:
-* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVExtractMergeBlocksVolumeSurfaceWell.
+* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVGeosBlockExtractAndMerge.
* Select the Geos output .pvd file loaded in Paraview.
-* Search and Apply PVExtractMergeBlocksVolumeSurfaceWell Filter.
-
+* Search and Apply PVGeosBlockExtractAndMerge Filter.
"""
+loggerTitle: str = "Extract & Merge GEOS Block"
+
@smproxy.filter(
- name="PVExtractMergeBlocksVolumeSurfaceWell",
- label="Geos Extract And Merge Blocks - Volume/Surface/Well",
+ name="PVGeosBlockExtractAndMerge",
+ label="Geos Extract And Merge Blocks",
)
-@smhint.xml( """
-
+@smproperty.xml( """
-
+
+
+
+
+
+
+
""" )
-@smproperty.input( name="Input", port_index=0 )
+@smproperty.input( name="GEOSpvd", port_index=0, label="GEOS pvd" )
@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
-class PVExtractMergeBlocksVolumeSurfaceWell( VTKPythonAlgorithmBase ):
+class PVGeosBlockExtractAndMerge( VTKPythonAlgorithmBase ):
def __init__( self: Self ) -> None:
"""Paraview plugin to extract and merge ranks from Geos output Mesh.
@@ -88,6 +97,9 @@ def __init__( self: Self ) -> None:
outputType="vtkMultiBlockDataSet",
)
+ self.extractFault: bool = False
+ self.extractWell: bool = False
+
#: all time steps from input
self.m_timeSteps: npt.NDArray[ np.float64 ] = np.array( [] )
#: displayed time step in the IHM
@@ -97,53 +109,9 @@ def __init__( self: Self ) -> None:
#: request data processing step - incremented each time RequestUpdateExtent is called
self.m_requestDataStep: int = -1
- # saved object at initial time step
- self.m_outputT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
-
- # set logger
- self.m_logger: Logger = getLogger( "Extract and merge block Filter" )
-
- def SetLogger( self: Self, logger: Logger ) -> None:
- """Set filter logger.
-
- Args:
- logger (Logger): logger
- """
- self.m_logger = logger
-
- 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(), "vtkMultiBlockDataSet" )
- return 1
-
- 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
+ self.logger = logging.getLogger( loggerTitle )
+ self.logger.setLevel( logging.INFO )
+ self.logger.addHandler( VTKHandler() )
def RequestDataObject(
self: Self,
@@ -209,7 +177,6 @@ def RequestUpdateExtent(
inInfo = inInfoVec[ 0 ]
# get displayed time step info before updating time
if self.m_requestDataStep == -1:
- self.m_logger.info( f"Apply filter {__name__}" )
self.m_timeSteps = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() # type: ignore
)
self.m_currentTime = inInfo.GetInformationObject( 0 ).Get( executive.UPDATE_TIME_STEP() # type: ignore
@@ -248,118 +215,109 @@ def RequestData(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
+ self.logger.info( f"Apply plugin { self.logger.name }." )
try:
- input: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
+ inputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
+ outputCellsT0: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
outputCells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
outputFaults: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 1 )
outputWells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 2 )
- assert input is not None, "Input MultiBlockDataSet is null."
- assert outputCells is not None, "Output volum mesh is null."
+ assert inputMesh is not None, "Input MultiBlockDataSet is null."
+ assert outputCells is not None, "Output volume mesh is null."
assert outputFaults is not None, "Output surface mesh is null."
assert outputWells is not None, "Output well mesh is null."
- # time controller
+ blockNames: list[ str ] = getBlockNames( inputMesh )
+ if GeosDomainNameEnum.VOLUME_DOMAIN_NAME.value in blockNames:
+ self.extractFault = True
+ if GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
+ self.extractWell = True
+
+ # Time controller, only the first and the current time step are computed
executive = self.GetExecutive()
if self.m_requestDataStep == 0:
- # first time step
- # do extraction and merge (do not display phase info)
- self.m_logger.setLevel( ERROR )
- outputFaults0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- outputWells0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- self.doExtractAndMerge( input, outputCells, outputFaults0, outputWells0 )
- self.m_logger.setLevel( INFO )
- # save input mesh to copy later
- self.m_outputT0.ShallowCopy( outputCells )
- request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore
- if self.m_requestDataStep >= self.m_currentTimeStepIndex:
- # displayed time step, no need to go further
+ self.doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells )
+ outputCellsT0.ShallowCopy( outputCells )
+ request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore
+ if self.m_requestDataStep == self.m_currentTimeStepIndex:
+ self.doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells )
+ # Copy attributes from the initial time step
+ meshAttributes: set[ str ] = getAttributeSet( outputCellsT0, False )
+ for ( attributeName, attributeNewName ) in getAttributeToTransferFromInitialTime().items():
+ if attributeName in meshAttributes:
+ copyAttribute( outputCellsT0, outputCells, attributeName, attributeNewName )
+ # Create elementCenter attribute in the volume mesh if needed
+ cellCenterAttributeName: str = GeosMeshOutputsEnum.ELEMENT_CENTER.attributeName
+ createCellCenterAttribute( outputCells, cellCenterAttributeName )
+
+ # Stop the computation
request.Remove( executive.CONTINUE_EXECUTING() ) # type: ignore
- # reinitialize requestDataStep if filter is recalled later
self.m_requestDataStep = -1
- # do extraction and merge
- self.doExtractAndMerge( input, outputCells, outputFaults, outputWells )
- # copy attributes from initial time step
- for (
- attributeName,
- attributeNewName,
- ) in getAttributeToTransferFromInitialTime().items():
- copyAttribute( self.m_outputT0, outputCells, attributeName, attributeNewName )
- # create elementCenter attribute in the volume mesh if needed
- cellCenterAttributeName: str = ( GeosMeshOutputsEnum.ELEMENT_CENTER.attributeName )
- createCellCenterAttribute( outputCells, cellCenterAttributeName )
except AssertionError as e:
- mess: str = "Block extraction and merge failed due to:"
- self.m_logger.error( mess )
- self.m_logger.error( str( e ) )
+ self.logger.error( f"The plugin failed.\n{e}" )
return 0
except Exception as e:
mess1: str = "Block extraction and merge failed due to:"
- self.m_logger.critical( mess1 )
- self.m_logger.critical( e, exc_info=True )
+ self.logger.critical( mess1 )
+ self.logger.critical( e, exc_info=True )
return 0
return 1
def doExtractAndMerge(
self: Self,
- input: vtkMultiBlockDataSet,
+ mesh: vtkMultiBlockDataSet,
outputCells: vtkMultiBlockDataSet,
outputFaults: vtkMultiBlockDataSet,
outputWells: vtkMultiBlockDataSet,
- ) -> bool:
+ ) -> None:
"""Apply block extraction and merge.
Args:
- input (vtkMultiBlockDataSet): input multi block
- outputCells (vtkMultiBlockDataSet): output volume mesh
- outputFaults (vtkMultiBlockDataSet): output surface mesh
- outputWells (vtkMultiBlockDataSet): output well mesh
-
- Returns:
- bool: True if extraction and merge successfully eneded, False otherwise
+ mesh (vtkMultiBlockDataSet): Mesh to process.
+ outputCells (vtkMultiBlockDataSet): Output volume mesh.
+ outputFaults (vtkMultiBlockDataSet): Output surface mesh.
+ outputWells (vtkMultiBlockDataSet): Output well mesh.
"""
- # extract blocks
- blockExtractor: GeosBlockExtractor = GeosBlockExtractor( input, extractFaults=True, extractWells=True )
+ # Extract blocks
+ blockExtractor: GeosBlockExtractor = GeosBlockExtractor( mesh, extractFault=self.extractFault, extractWell=self.extractWell, speHandler=True )
+ if not blockExtractor.logger.hasHandlers():
+ blockExtractor.setLoggerHandler( VTKHandler() )
blockExtractor.applyFilter()
# recover output objects from GeosBlockExtractor filter and merge internal blocks
volumeBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.volume
- assert volumeBlockExtracted is not None, "Extracted Volume mesh is null."
outputCells.ShallowCopy( self.mergeBlocksFilter( volumeBlockExtracted, False ) )
+ outputCells.Modified()
- faultBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.fault
- assert faultBlockExtracted is not None, "Extracted Fault mesh is null."
- outputFaults.ShallowCopy( self.mergeBlocksFilter( faultBlockExtracted, True ) )
-
- wellBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.well
- assert wellBlockExtracted is not None, "Extracted Well mesh is null."
- outputWells.ShallowCopy( self.mergeBlocksFilter( wellBlockExtracted, False ) )
+ if self.extractFault:
+ faultBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.fault
+ outputFaults.ShallowCopy( self.mergeBlocksFilter( faultBlockExtracted, True ) )
+ outputFaults.Modified()
- outputCells.Modified()
- outputFaults.Modified()
- outputWells.Modified()
+ if self.extractWell:
+ wellBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.well
+ outputWells.ShallowCopy( self.mergeBlocksFilter( wellBlockExtracted, False ) )
+ outputWells.Modified()
- self.m_logger.info( "Volume blocks were successfully splitted from surfaces" +
- " and wells, and ranks were merged together." )
- return True
+ return
def mergeBlocksFilter( self: Self,
- input: vtkMultiBlockDataSet,
+ mesh: vtkMultiBlockDataSet,
convertSurfaces: bool = False ) -> vtkMultiBlockDataSet:
"""Apply vtk merge block filter on input multi block mesh.
Args:
- input (vtkMultiBlockDataSet): multiblock mesh to merge
- convertSurfaces (bool, optional): True to convert surface from vtkUnstructuredGrid to
- vtkPolyData. Defaults to False.
+ mesh (vtkMultiBlockDataSet): Mesh to merge.
+ convertSurfaces (bool, optional): True to convert surface from vtkUnstructuredGrid to vtkPolyData.
+ Defaults to False.
Returns:
- vtkMultiBlockDataSet: Multiblock mesh composed of internal merged blocks.
+ vtkMultiBlockDataSet: Mesh composed of internal merged blocks.
"""
- mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( input, convertSurfaces, True )
+ mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( mesh, convertSurfaces, True )
if not mergeBlockFilter.logger.hasHandlers():
mergeBlockFilter.setLoggerHandler( VTKHandler() )
mergeBlockFilter.applyFilter()
mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
- assert mergedBlocks is not None, "Final merged MultiBlockDataSet is null."
return mergedBlocks
From efc53be1fec539f0f60a7fe15af859459b57d689 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 6 Nov 2025 11:59:08 +0100
Subject: [PATCH 36/62] fix timestep parsing
---
.../PVExtractMergeBlocksVolumeSurfaceWell.py | 63 +++++++++++++++----
1 file changed, 50 insertions(+), 13 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
index 45553b87d..da46fb1d3 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
@@ -1,6 +1,6 @@
# 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 sys
from pathlib import Path
@@ -109,10 +109,46 @@ def __init__( self: Self ) -> None:
#: request data processing step - incremented each time RequestUpdateExtent is called
self.m_requestDataStep: int = -1
+ self.outputCellsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
+
self.logger = logging.getLogger( loggerTitle )
self.logger.setLevel( logging.INFO )
self.logger.addHandler( VTKHandler() )
+ 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(), "vtkMultiBlockDataSet" )
+ return 1
+
+ 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,
request: vtkInformation,
@@ -216,9 +252,9 @@ def RequestData(
int: 1 if calculation successfully ended, 0 otherwise.
"""
self.logger.info( f"Apply plugin { self.logger.name }." )
+ print(self.m_requestDataStep)
try:
inputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
- outputCellsT0: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
outputCells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
outputFaults: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 1 )
outputWells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 2 )
@@ -228,25 +264,25 @@ def RequestData(
assert outputFaults is not None, "Output surface mesh is null."
assert outputWells is not None, "Output well mesh is null."
- blockNames: list[ str ] = getBlockNames( inputMesh )
- if GeosDomainNameEnum.VOLUME_DOMAIN_NAME.value in blockNames:
- self.extractFault = True
- if GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
- self.extractWell = True
-
# Time controller, only the first and the current time step are computed
executive = self.GetExecutive()
if self.m_requestDataStep == 0:
- self.doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells )
- outputCellsT0.ShallowCopy( outputCells )
+ blockNames: list[ str ] = getBlockNames( inputMesh )
+ if GeosDomainNameEnum.VOLUME_DOMAIN_NAME.value in blockNames:
+ self.extractFault = True
+ if GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
+ self.extractWell = True
+ outputFaultsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
+ outputWellsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
+ self.doExtractAndMerge( inputMesh, self.outputCellsT0, outputFaultsT0, outputWellsT0 )
request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore
if self.m_requestDataStep == self.m_currentTimeStepIndex:
self.doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells )
# Copy attributes from the initial time step
- meshAttributes: set[ str ] = getAttributeSet( outputCellsT0, False )
+ meshAttributes: set[ str ] = getAttributeSet( self.outputCellsT0, False )
for ( attributeName, attributeNewName ) in getAttributeToTransferFromInitialTime().items():
if attributeName in meshAttributes:
- copyAttribute( outputCellsT0, outputCells, attributeName, attributeNewName )
+ copyAttribute( self.outputCellsT0, outputCells, attributeName, attributeNewName )
# Create elementCenter attribute in the volume mesh if needed
cellCenterAttributeName: str = GeosMeshOutputsEnum.ELEMENT_CENTER.attributeName
createCellCenterAttribute( outputCells, cellCenterAttributeName )
@@ -319,5 +355,6 @@ def mergeBlocksFilter( self: Self,
if not mergeBlockFilter.logger.hasHandlers():
mergeBlockFilter.setLoggerHandler( VTKHandler() )
mergeBlockFilter.applyFilter()
- mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
+ mergedBlocks: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
+ mergedBlocks.ShallowCopy( mergeBlockFilter.getOutput() )
return mergedBlocks
From f2a98a094e1200c4530dd7e4cf8b9e59955b9cf5 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 6 Nov 2025 11:59:44 +0100
Subject: [PATCH 37/62] fix computePhaseName
---
.../src/geos/processing/post_processing/GeosBlockMerge.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index b1188ddf5..d131d7595 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -196,7 +196,7 @@ def computePhaseNames( self: Self ) -> None:
"""Get the names of the phases in the mesh from Cell attributes."""
# All the phase attributes are on cells
for name in getAttributeSet( self.inputMesh, False ):
- if PHASE_SEP in name:
+ if PHASE_SEP in name and "dofIndex" not in name:
phaseName: str
suffixName: str
phaseName, suffixName = name.split( PHASE_SEP )
From db039413bb6f7d114952170fcf8430596b5b4488 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 6 Nov 2025 12:07:03 +0100
Subject: [PATCH 38/62] clean log
---
.../geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
index da46fb1d3..2f2bcf956 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
@@ -282,7 +282,7 @@ def RequestData(
meshAttributes: set[ str ] = getAttributeSet( self.outputCellsT0, False )
for ( attributeName, attributeNewName ) in getAttributeToTransferFromInitialTime().items():
if attributeName in meshAttributes:
- copyAttribute( self.outputCellsT0, outputCells, attributeName, attributeNewName )
+ copyAttribute( self.outputCellsT0, outputCells, attributeName, attributeNewName, False, self.logger )
# Create elementCenter attribute in the volume mesh if needed
cellCenterAttributeName: str = GeosMeshOutputsEnum.ELEMENT_CENTER.attributeName
createCellCenterAttribute( outputCells, cellCenterAttributeName )
From 4a380b086446f6164683fa1d39625d1d37ea5b78 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 6 Nov 2025 18:09:32 +0100
Subject: [PATCH 39/62] clean output
---
.../PVExtractMergeBlocksVolumeSurfaceWell.py | 71 +++++++++++--------
1 file changed, 41 insertions(+), 30 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
index 2f2bcf956..4ea0f1a24 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
@@ -70,7 +70,7 @@
label="Geos Extract And Merge Blocks",
)
@smproperty.xml( """
-
+
@@ -97,8 +97,9 @@ def __init__( self: Self ) -> None:
outputType="vtkMultiBlockDataSet",
)
- self.extractFault: bool = False
- self.extractWell: bool = False
+ self.extractFault: bool = True
+ self.extractWell: bool = True
+ self.wellId: int = 2
#: all time steps from input
self.m_timeSteps: npt.NDArray[ np.float64 ] = np.array( [] )
@@ -167,8 +168,6 @@ def RequestDataObject(
"""
inData = self.GetInputData( inInfoVec, 0, 0 )
outDataCells = self.GetOutputData( outInfoVec, 0 )
- outDataFaults = self.GetOutputData( outInfoVec, 1 )
- outDataWells = self.GetOutputData( outInfoVec, 2 )
assert inData is not None
if outDataCells is None or ( not outDataCells.IsA( "vtkMultiBlockDataSet" ) ):
outDataCells = vtkMultiBlockDataSet()
@@ -177,19 +176,22 @@ def RequestDataObject(
outDataCells # type: ignore
)
- if outDataFaults is None or ( not outDataFaults.IsA( "vtkMultiBlockDataSet" ) ):
- outDataFaults = vtkMultiBlockDataSet()
- outInfoVec.GetInformationObject( 1 ).Set(
- outDataFaults.DATA_OBJECT(),
- outDataFaults # type: ignore
- )
-
- if outDataWells is None or ( not outDataWells.IsA( "vtkMultiBlockDataSet" ) ):
- outDataWells = vtkMultiBlockDataSet()
- outInfoVec.GetInformationObject( 2 ).Set(
- outDataWells.DATA_OBJECT(),
- outDataWells # type: ignore
- )
+ if self.extractFault:
+ outDataFaults = self.GetOutputData( outInfoVec, 1 )
+ if outDataFaults is None or ( not outDataFaults.IsA( "vtkMultiBlockDataSet" ) ):
+ outDataFaults = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 1 ).Set(
+ outDataFaults.DATA_OBJECT(),
+ outDataFaults # type: ignore
+ )
+ if self.extractWell:
+ outDataWells = self.GetOutputData( outInfoVec, self.wellId )
+ if outDataWells is None or ( not outDataWells.IsA( "vtkMultiBlockDataSet" ) ):
+ outDataWells = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 2 ).Set(
+ outDataWells.DATA_OBJECT(),
+ outDataWells # type: ignore
+ )
return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return]
@@ -255,28 +257,37 @@ def RequestData(
print(self.m_requestDataStep)
try:
inputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
- outputCells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
- outputFaults: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 1 )
- outputWells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 2 )
-
- assert inputMesh is not None, "Input MultiBlockDataSet is null."
- assert outputCells is not None, "Output volume mesh is null."
- assert outputFaults is not None, "Output surface mesh is null."
- assert outputWells is not None, "Output well mesh is null."
# Time controller, only the first and the current time step are computed
executive = self.GetExecutive()
if self.m_requestDataStep == 0:
blockNames: list[ str ] = getBlockNames( inputMesh )
- if GeosDomainNameEnum.VOLUME_DOMAIN_NAME.value in blockNames:
- self.extractFault = True
- if GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
- self.extractWell = True
+ if not GeosDomainNameEnum.VOLUME_DOMAIN_NAME.value in blockNames:
+ self.extractFault = False
+ outInfoVec.GetInformationObject( 1 ).Remove( vtkMultiBlockDataSet.DATA_OBJECT() )
+ self.wellId = 1
+
+ if not GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
+ self.extractWell = False
+ outInfoVec.GetInformationObject( self.wellId ).Remove( vtkMultiBlockDataSet.DATA_OBJECT() )
+
outputFaultsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
outputWellsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
self.doExtractAndMerge( inputMesh, self.outputCellsT0, outputFaultsT0, outputWellsT0 )
request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore
if self.m_requestDataStep == self.m_currentTimeStepIndex:
+ outputCells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
+ outputFaults: vtkMultiBlockDataSet
+ outputWells: vtkMultiBlockDataSet
+ if self.extractFault:
+ outputFaults = self.GetOutputData( outInfoVec, 1 )
+ else:
+ outputFaults = vtkMultiBlockDataSet()
+ if self.extractWell:
+ outputWells = self.GetOutputData( outInfoVec, self.wellId )
+ else:
+ outputWells = vtkMultiBlockDataSet()
+
self.doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells )
# Copy attributes from the initial time step
meshAttributes: set[ str ] = getAttributeSet( self.outputCellsT0, False )
From 5761f7bff859cd925e384e4610f5e3986637a965 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 6 Nov 2025 18:09:55 +0100
Subject: [PATCH 40/62] Update Geos Output array names
---
.../src/geos/utils/GeosOutputsConstants.py | 25 ++++++++++++++++---
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/geos-utils/src/geos/utils/GeosOutputsConstants.py b/geos-utils/src/geos/utils/GeosOutputsConstants.py
index 52d50a3f8..ac4190002 100644
--- a/geos-utils/src/geos/utils/GeosOutputsConstants.py
+++ b/geos-utils/src/geos/utils/GeosOutputsConstants.py
@@ -95,17 +95,34 @@ class GeosMeshSuffixEnum( Enum ):
STRAIN_SUFFIX = "strain"
PERMEABILITY_SUFFIX = "_permeability"
POROSITY_SUFFIX = "_porosity"
+ POROSITY_INIT_SUFFIX = "_initialPorosity"
POROSITY_REF_SUFFIX = "_referencePorosity"
BULK_MODULUS_SUFFIX = "_bulkModulus"
SHEAR_MODULUS_SUFFIX = "_shearModulus"
GRAIN_BULK_MODULUS_SUFFIX = "_grainBulkModulus"
BIOT_COEFFICIENT_SUFFIX = "_biotCoefficient"
- # fluid attributes suffix
- PHASE_DENSITY_SUFFIX = "_density"
- PHASE_MASS_DENSITY_SUFFIX = "_phaseMassDensity"
- PHASE_VISCOSITY_SUFFIX = "_viscosity"
+ # fluid attributes suffix multiPhases
+ TOTAL_FLUID_DENSITY_SUFFIX = "_totalDensity"
+ PHASE_DENSITY_SUFFIX = "_phaseDensity"
PHASE_FRACTION_SUFFIX = "_phaseFraction"
+ PHASE_MASS_DENSITY_SUFFIX = "_phaseMassDensity"
+ PHASE_VISCOSITY_SUFFIX = "_phaseViscosity"
+ PHASE_INTERNAL_ENERGY_SUFFIX = "_phaseInternalEnergy"
+ PHASE_COMP_FRACTION_SUFFIX = "_phaseCompFraction"
+ PHASE_REAL_PERM_SUFFIX = "_phaseRelPerm"
+ PHASE_TRAP_VOL_FRACTION_SUFFIX = "_phaseTrappedVolFraction"
+
+ # fluid attributes suffix singlePhase
+ FLUID_DENSITY_SUFFIX = "_density"
+ FLUID_VISCOSITY_SUFFIX = "_viscosity"
+ FLUID_DENSITY_DERIVATE_SUFFIX = "_dDensity"
+ FLUID_VISCOSITY_DERIVATE_SUFFIX = "_dViscosity"
+ FLUID_INTERNAL_ENERGY_SUFFIX = "_internalEnergy"
+ FLUID_INTERNAL_ENERGY_DERIVATE_SUFFIX = "_dInternalEnergy"
+ FLUID_ENTHALPY_SUFFIX = "_enthalpy"
+ FLUID_ENTHALPY_DERIVATE_SUFFIX = "_dEnthalpy"
+
# surface attribute transfer suffix
SURFACE_PLUS_SUFFIX = "_Plus"
From a95538209721e2cf24ce68d72ed62848509d8bc3 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 6 Nov 2025 18:19:28 +0100
Subject: [PATCH 41/62] fix error
---
.../geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
index 4ea0f1a24..f0dde540b 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
@@ -264,12 +264,9 @@ def RequestData(
blockNames: list[ str ] = getBlockNames( inputMesh )
if not GeosDomainNameEnum.VOLUME_DOMAIN_NAME.value in blockNames:
self.extractFault = False
- outInfoVec.GetInformationObject( 1 ).Remove( vtkMultiBlockDataSet.DATA_OBJECT() )
- self.wellId = 1
if not GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
self.extractWell = False
- outInfoVec.GetInformationObject( self.wellId ).Remove( vtkMultiBlockDataSet.DATA_OBJECT() )
outputFaultsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
outputWellsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
From 7c76c64d83d27201d778e40b871f4c19039df840 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Fri, 7 Nov 2025 15:33:27 +0100
Subject: [PATCH 42/62] Apply Paloma suggestion
---
.../src/geos/mesh/utils/genericHelpers.py | 133 +++++++++++++++---
.../post_processing/GeosBlockMerge.py | 61 +++-----
2 files changed, 138 insertions(+), 56 deletions(-)
diff --git a/geos-mesh/src/geos/mesh/utils/genericHelpers.py b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
index 0e2ebb39e..c766173ab 100644
--- a/geos-mesh/src/geos/mesh/utils/genericHelpers.py
+++ b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
@@ -1,31 +1,24 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Martin Lemay, Paloma Martinez
-import numpy as np
import logging
+import numpy as np
import numpy.typing as npt
-from typing import Iterator, List, Sequence, Any, Union, Tuple
+from typing import ( Iterator, List, Sequence, Any, Union, Tuple )
+
from vtkmodules.util.numpy_support import ( numpy_to_vtk, vtk_to_numpy )
from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints, reference, vtkDataArray, vtkLogger, vtkFloatArray
-from vtkmodules.vtkCommonDataModel import (
- vtkUnstructuredGrid,
- vtkMultiBlockDataSet,
- vtkPolyData,
- vtkDataSet,
- vtkDataObject,
- vtkPlane,
- vtkCellTypes,
- vtkIncrementalOctreePointLocator,
-)
+from vtkmodules.vtkCommonDataModel import ( vtkUnstructuredGrid, vtkMultiBlockDataSet, vtkPolyData, vtkDataSet,
+ vtkDataObject, vtkPlane, vtkCellTypes, vtkIncrementalOctreePointLocator,
+ VTK_TRIANGLE )
from vtkmodules.vtkFiltersCore import ( vtk3DLinearGridPlaneCutter, vtkPolyDataNormals, vtkPolyDataTangents )
from vtkmodules.vtkFiltersTexture import vtkTextureMapToPlane
+from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter
+from vtkmodules.vtkFiltersGeneral import vtkDataSetTriangleFilter
from geos.mesh.utils.multiblockHelpers import ( getBlockElementIndexesFlatten, getBlockFromFlatIndex )
-from geos.utils.algebraFunctions import (
- getAttributeMatrixFromVector,
- getAttributeVectorFromMatrix,
-)
+from geos.utils.algebraFunctions import ( getAttributeMatrixFromVector, getAttributeVectorFromMatrix )
from geos.utils.geometryFunctions import ( getChangeOfBasisMatrix, CANONICAL_BASIS_3D )
from geos.utils.Logger import ( getLogger, Logger, VTKCaptureLog, RegexExceptionFilter )
from geos.utils.Errors import VTKError
@@ -40,6 +33,114 @@
"""
+def isTriangulate( dataSet: vtkUnstructuredGrid ) -> bool:
+ """Check if the mesh is triangulate only.
+
+ Args:
+ dataSet (vtkUnstructuredGrid): The mesh to check
+
+ Returns:
+ bool: True if the mesh is triangulate only, False otherwise.
+ """
+ cellTypes: vtkCellTypes = vtkCellTypes()
+ dataSet.GetCellTypes( cellTypes )
+
+ return all( cellTypes.GetCellType( cell ) == VTK_TRIANGLE for cell in range( cellTypes.GetNumberOfTypes() ) )
+
+
+def triangulateMesh(
+ dataSet: vtkUnstructuredGrid,
+ logger: Union[ Logger, None ] = None,
+) -> vtkUnstructuredGrid:
+ """Triangulate any type of dataset.
+
+ Args:
+ dataSet (vtkUnstructuredGrid): The mesh to triangulate
+ logger (Union[Logger, None], optional): A logger to manage the output messages.
+ Defaults to None, an internal logger is used.
+
+ Returns:
+ vtkUnstructuredGrid: Triangulated mesh
+ """
+ if logger is None:
+ logger = getLogger( "Triangulate", True )
+ # Creation of a child logger to deal with VTKErrors without polluting parent logger
+ vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger", True )
+ vtkErrorLogger.propagate = False
+
+ vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR )
+
+ vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error
+ vtkErrorLogger.setLevel( logging.DEBUG )
+
+ with VTKCaptureLog() as capturedLog:
+ triangulateMeshFilter: vtkDataSetTriangleFilter = vtkDataSetTriangleFilter()
+ triangulateMeshFilter.SetInputData( dataSet )
+ triangulateMeshFilter.Update()
+
+ capturedLog.seek( 0 )
+ captured = capturedLog.read().decode()
+
+ vtkErrorLogger.debug( captured.strip() )
+
+ triangulatedMesh: vtkUnstructuredGrid = triangulateMeshFilter.GetOutput()
+
+ if triangulatedMesh.GetCellData() is None:
+ raise VTKError( "Something went wrong with VTK triangulation of the mesh." )
+
+ return triangulatedMesh
+
+
+def convertUnstructuredGridToPolyData(
+ dataSet: vtkUnstructuredGrid,
+ logger: Union[ Logger, None ] = None,
+) -> vtkPolyData:
+ """Convert vtkUnstructuredGrid to vtkPolyData.
+
+ ..Warning:: Work only with triangulated mesh
+
+ Args:
+ dataSet (vtkUnstructuredGrid): Input mesh block
+ logger (Union[Logger, None], optional): A logger to manage the output messages.
+ Defaults to None, an internal logger is used.
+
+ Returns:
+ vtkPolyData: Extracted surface
+ """
+ if logger is None:
+ logger = getLogger( "ConvertVtkUnstructuredGridToVtkPolyData.", True )
+ # Creation of a child logger to deal with VTKErrors without polluting parent logger
+ vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger", True )
+ vtkErrorLogger.propagate = False
+
+ vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR )
+
+ vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error
+ vtkErrorLogger.setLevel( logging.DEBUG )
+
+ with VTKCaptureLog() as capturedLog:
+ extractSurfaceFilter: vtkDataSetSurfaceFilter = vtkDataSetSurfaceFilter()
+ extractSurfaceFilter.SetInputData( dataSet )
+ # fast mode should be used for rendering only
+ extractSurfaceFilter.FastModeOff()
+ # Delegation activated allow to accelerate the processing with unstructured mesh
+ # see https://vtk.org/doc/nightly/html/classvtkDataSetSurfaceFilter.html
+ extractSurfaceFilter.DelegationOn()
+ extractSurfaceFilter.Update()
+
+ capturedLog.seek( 0 )
+ captured = capturedLog.read().decode()
+
+ vtkErrorLogger.debug( captured.strip() )
+
+ extractedSurface: vtkPolyData = extractSurfaceFilter.GetOutput()
+
+ if extractedSurface is None:
+ raise VTKError( "Something went wrong with VTK convert vtu to vtp." )
+
+ return extractedSurface
+
+
def toVtkIdList( data: List[ int ] ) -> vtkIdList:
"""Utility function transforming a list of ids into a vtkIdList.
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index b1188ddf5..ef4e54c78 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
-# # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Martin Lemay, Romain Baville
# ruff: noqa: E402 # disable Module level import not at top of file
import logging
@@ -7,25 +7,31 @@
from vtkmodules.vtkCommonDataModel import ( vtkCompositeDataSet, vtkMultiBlockDataSet, vtkPolyData,
vtkUnstructuredGrid )
-from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter
+from geos.utils.Errors import VTKError
from geos.utils.Logger import ( Logger, getLogger )
from geos.utils.GeosOutputsConstants import ( PHASE_SEP, PhaseTypeEnum, FluidPrefixEnum, PostProcessingOutputsEnum,
getRockSuffixRenaming )
+
from geos.mesh.utils.arrayHelpers import getAttributeSet
from geos.mesh.utils.arrayModifiers import ( createConstantAttribute, renameAttribute )
from geos.mesh.utils.multiblockModifiers import mergeBlocks
from geos.mesh.utils.multiblockHelpers import ( getElementaryCompositeBlockIndexes, extractBlock )
-from geos.mesh.utils.genericHelpers import ( computeNormals, computeTangents )
+from geos.mesh.utils.genericHelpers import ( computeNormals, computeTangents, convertTriangulateBlockToSurface,
+ triangulateMesh, isTriangulate )
__doc__ = """
-GeosBlockMerge is a vtk filter that allows to merge for a GEOS domain the ranks per region, identify "Fluids" and "Rock" phases and rename "Rock" attributes.
+GeosBlockMerge is a vtk filter that acts on each region of a GEOS output domain (volume, fault, wells):
+ - Ranks are merged.
+ - "Fluids" and "Rock" phases are identified.
+ - "Rock" attributes are renamed depending on the phase they refer to for more clarity.
+ - Volume meshes are converted to surface if needed.
Filter input and output types are vtkMultiBlockDataSet.
.. Important::
- This filter deals with the domain mesh of GEOS. This domain needs to be extracted before.
- See geos-processing/src/geos/processing/post_processing/GeosBlockExtractor.py to see the type of input requires by this filter.
+ This filter cannot be used directly on GEOS output. The domain needs to be extracted with the help of `GeosBlockExtractor` filter.
+ Please refer to the `documentation `_ for more information.
To use the filter:
@@ -66,14 +72,14 @@ def __init__(
) -> None:
"""VTK Filter that merge ranks of GEOS output mesh.
- for all the composite blocks of the input mesh:
- - Ranks are merged
- - "Rock" attributes are renamed
- - Volume mesh are convert to surface if needed
+ For each composite block of the input mesh:
+ - Ranks are merged.
+ - "Rock" attributes are renamed.
+ - Volume meshes are converted to surface if requested.
Args:
inputMesh (vtkMultiBlockDataSet): The mesh with the blocks to merge.
- convertFaultToSurface (bool, optional): True if the merged block need to be convert to vtp, False otherwise.
+ convertFaultToSurface (bool, optional): If True, merged blocks are converted to surface (vtp), False otherwise.
Defaults to False.
speHandler (bool, optional): True to use a specific handler, False to use the internal handler.
Defaults to False.
@@ -154,19 +160,18 @@ def applyFilter( self: Self ) -> None:
# Convert the volume mesh to a surface mesh
if self.convertFaultToSurface:
- surfaceMesh: vtkPolyData = self.convertBlockToSurface( volumeMesh )
- assert surfaceMesh is not None, "Surface extraction from block failed."
+ if not isTriangulate( volumeMesh ):
+ volumeMesh.ShallowCopy( triangulateMesh( volumeMesh ) )
+ surfaceMesh: vtkPolyData = convertTriangulateBlockToSurface( volumeMesh )
surfaceMesh.ShallowCopy( computeNormals( surfaceMesh, logger=self.logger ) )
- assert surfaceMesh is not None, "Normal calculation failed."
surfaceMesh.ShallowCopy( computeTangents( surfaceMesh, logger=self.logger ) )
- assert surfaceMesh is not None, "Tangent calculation failed."
# Add the merged block to the output mesh
self.outputMesh.SetBlock( newIndex, surfaceMesh )
else:
self.outputMesh.SetBlock( newIndex, volumeMesh )
self.logger.info( "The filter succeeded." )
- except ( ValueError, TypeError, RuntimeError, AssertionError ) as e:
+ except ( ValueError, TypeError, RuntimeError, AssertionError, VTKError ) as e:
self.logger.critical( f"The filter failed.\n{ e }" )
return
@@ -212,27 +217,3 @@ def computePhaseNames( self: Self ) -> None:
self.phaseNameDict[ PhaseTypeEnum.FLUID.type ].add( phaseName )
return
-
- def convertBlockToSurface( self: Self, block: vtkUnstructuredGrid ) -> vtkPolyData:
- """Convert vtkUnstructuredGrid to a surface vtkPolyData.
-
- .. WARNING:: work only with triangulated surfaces
-
- .. TODO:: need to convert quadrangular to triangulated surfaces first
-
- Args:
- block (vtkUnstructuredGrid): block from which to extract the surface
-
- Returns:
- vtkPolyData: extracted surface
- """
- extractSurfaceFilter: vtkDataSetSurfaceFilter = vtkDataSetSurfaceFilter()
- extractSurfaceFilter.SetInputData( block )
- # fast mode should be used for rendering only
- extractSurfaceFilter.FastModeOff()
- # Delegation activated allow to accelerate the processing with unstructured mesh
- # see https://vtk.org/doc/nightly/html/classvtkDataSetSurfaceFilter.html
- extractSurfaceFilter.DelegationOn()
- extractSurfaceFilter.Update()
- output: vtkPolyData = extractSurfaceFilter.GetOutput()
- return output
From de12113ed0252c4b9074f75ef77760635244f55b Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Fri, 7 Nov 2025 19:14:41 +0100
Subject: [PATCH 43/62] Update the doc and clean the interface
---
.../PVExtractMergeBlocksVolumeSurfaceWell.py | 127 ++++++++++--------
1 file changed, 71 insertions(+), 56 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
index f0dde540b..78facdaad 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
@@ -3,14 +3,12 @@
# SPDX-FileContributor: Martin Lemay, Romain Baville
# ruff: noqa: E402 # disable Module level import not at top of file
import sys
-from pathlib import Path
-
-import numpy as np
import logging
+import numpy as np
import numpy.typing as npt
+
+from pathlib import Path
from typing_extensions import Self
-from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector
-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
@@ -19,41 +17,58 @@
update_paths()
-from geos.utils.GeosOutputsConstants import (
- GeosMeshOutputsEnum,
- GeosDomainNameEnum,
- getAttributeToTransferFromInitialTime,
-)
from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
+
from geos.mesh.utils.arrayHelpers import getAttributeSet
from geos.mesh.utils.arrayModifiers import (
copyAttribute,
- createCellCenterAttribute,
+ createCellCenterAttribute
)
from geos.mesh.utils.multiblockHelpers import getBlockNames
+
+from geos.utils.GeosOutputsConstants import (
+ GeosMeshOutputsEnum,
+ GeosDomainNameEnum,
+ getAttributeToTransferFromInitialTime
+)
+
from geos.pv.utils.paraviewTreatments import getTimeStepIndex
+
+from vtkmodules.vtkCommonCore import ( vtkInformation, vtkInformationVector )
+from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet
+
from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
- VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
+ VTKPythonAlgorithmBase, smdomain, smproperty, smproxy
)
from paraview.detail.loghandler import ( # type: ignore[import-not-found]
- VTKHandler,
+ VTKHandler
) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py
__doc__ = """
-PVGeosBlockExtractAndMerge is a Paraview plugin that allows to merge
-ranks of a Geos output objects containing a volumic mesh, surfaces, and wells.
-
-Input and output types are vtkMultiBlockDataSet.
-
-This filter results in 3 output pipelines:
-
-* first pipeline contains the volume mesh. If multiple regions were defined in
- the volume mesh, they are preserved as distinct blocks.
-* second pipeline contains surfaces. If multiple surfaces were used, they are
- preserved as distinct blocks.
-* third pipeline contains wells. If multiple wells were used, they are preserved
- as distinct blocks.
+PVGeosBlockExtractAndMerge is a Paraview plugin that:
+ Allows to extract domains (volume, fault and well) from a GEOS output multiBlockDataSet mesh
+ Acts on each region of a GEOS output domain (volume, fault, wells) to:
+ Merge Ranks
+ Identify "Fluids" and "Rock" phases
+ Rename "Rock" attributes depending on the phase they refer to for more clarity
+ Convert volume meshes to surface if needed
+ Copy "geomechanics" attributes from the initial timestep to the current one if their exist
+
+This filter results in 3 output pipelines with the vtkMultiBlockDataSet:
+ "Volume" contains the volume domain
+ "Fault" contains the fault domain if it exist
+ "Well" contains the well domain if it exist
+
+Input and output meshes are vtkMultiBlockDataSet.
+
+.. Important::
+ The input mesh must be an output of a GEOS simulation or contain at least three blocks labeled with the same domain names:
+ "CellElementRegion" for volume domain
+ "SurfaceElementRegion" for fault domain if the input contains fault
+ "WellElementRegion" for well domain if the input contains well
+ See more https://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/datastructure/ElementRegions.html?_sm_au_=iVVT5rrr5fN00R8sQ0WpHK6H8sjL6#xml-element-elementregions
+ The filter detected automatically the three domains. If one of them if not in the input mesh, the associated output pipeline will be empty
To use it:
@@ -67,12 +82,12 @@
@smproxy.filter(
name="PVGeosBlockExtractAndMerge",
- label="Geos Extract And Merge Blocks",
+ label="Geos Extract and Merge Blocks",
)
@smproperty.xml( """
-
-
-
+
+
+
@@ -80,7 +95,7 @@
""" )
-@smproperty.input( name="GEOSpvd", port_index=0, label="GEOS pvd" )
+@smproperty.input( name="Input", port_index=0 )
@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
class PVGeosBlockExtractAndMerge( VTKPythonAlgorithmBase ):
@@ -97,9 +112,8 @@ def __init__( self: Self ) -> None:
outputType="vtkMultiBlockDataSet",
)
- self.extractFault: bool = True
- self.extractWell: bool = True
- self.wellId: int = 2
+ self.extractFault: bool = False
+ self.extractWell: bool = False
#: all time steps from input
self.m_timeSteps: npt.NDArray[ np.float64 ] = np.array( [] )
@@ -168,6 +182,8 @@ def RequestDataObject(
"""
inData = self.GetInputData( inInfoVec, 0, 0 )
outDataCells = self.GetOutputData( outInfoVec, 0 )
+ outDataFaults = self.GetOutputData( outInfoVec, 1 )
+ outDataWells = self.GetOutputData( outInfoVec, 2 )
assert inData is not None
if outDataCells is None or ( not outDataCells.IsA( "vtkMultiBlockDataSet" ) ):
outDataCells = vtkMultiBlockDataSet()
@@ -176,22 +192,19 @@ def RequestDataObject(
outDataCells # type: ignore
)
- if self.extractFault:
- outDataFaults = self.GetOutputData( outInfoVec, 1 )
- if outDataFaults is None or ( not outDataFaults.IsA( "vtkMultiBlockDataSet" ) ):
- outDataFaults = vtkMultiBlockDataSet()
- outInfoVec.GetInformationObject( 1 ).Set(
- outDataFaults.DATA_OBJECT(),
- outDataFaults # type: ignore
- )
- if self.extractWell:
- outDataWells = self.GetOutputData( outInfoVec, self.wellId )
- if outDataWells is None or ( not outDataWells.IsA( "vtkMultiBlockDataSet" ) ):
- outDataWells = vtkMultiBlockDataSet()
- outInfoVec.GetInformationObject( 2 ).Set(
- outDataWells.DATA_OBJECT(),
- outDataWells # type: ignore
- )
+ if outDataFaults is None or ( not outDataFaults.IsA( "vtkMultiBlockDataSet" ) ):
+ outDataFaults = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 1 ).Set(
+ outDataFaults.DATA_OBJECT(),
+ outDataFaults # type: ignore
+ )
+
+ if outDataWells is None or ( not outDataWells.IsA( "vtkMultiBlockDataSet" ) ):
+ outDataWells = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 2 ).Set(
+ outDataWells.DATA_OBJECT(),
+ outDataWells # type: ignore
+ )
return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return]
@@ -262,11 +275,11 @@ def RequestData(
executive = self.GetExecutive()
if self.m_requestDataStep == 0:
blockNames: list[ str ] = getBlockNames( inputMesh )
- if not GeosDomainNameEnum.VOLUME_DOMAIN_NAME.value in blockNames:
- self.extractFault = False
+ if GeosDomainNameEnum.VOLUME_DOMAIN_NAME.value in blockNames:
+ self.extractFault = True
- if not GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
- self.extractWell = False
+ if GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
+ self.extractWell = True
outputFaultsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
outputWellsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
@@ -280,10 +293,13 @@ def RequestData(
outputFaults = self.GetOutputData( outInfoVec, 1 )
else:
outputFaults = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 1 ).Remove( vtkMultiBlockDataSet().DATA_OBJECT() )
+
if self.extractWell:
- outputWells = self.GetOutputData( outInfoVec, self.wellId )
+ outputWells = self.GetOutputData( outInfoVec, 2 )
else:
outputWells = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 2 ).Remove( vtkMultiBlockDataSet().DATA_OBJECT() )
self.doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells )
# Copy attributes from the initial time step
@@ -294,7 +310,6 @@ def RequestData(
# Create elementCenter attribute in the volume mesh if needed
cellCenterAttributeName: str = GeosMeshOutputsEnum.ELEMENT_CENTER.attributeName
createCellCenterAttribute( outputCells, cellCenterAttributeName )
-
# Stop the computation
request.Remove( executive.CONTINUE_EXECUTING() ) # type: ignore
self.m_requestDataStep = -1
From 2dd0c9c4eeeac1ee7520e56ec69d09b16acfa388 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Wed, 12 Nov 2025 08:45:35 +0100
Subject: [PATCH 44/62] Move utils functions from the plugin to be
generalisable
---
.../PVExtractMergeBlocksVolumeSurfaceWell.py | 105 +++---------------
.../src/geos/pv/utils/workflowFunctions.py | 72 ++++++++++++
2 files changed, 87 insertions(+), 90 deletions(-)
create mode 100644 geos-pv/src/geos/pv/utils/workflowFunctions.py
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
index 78facdaad..7f27a49fb 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
@@ -17,33 +17,20 @@
update_paths()
-from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
-from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
-
from geos.mesh.utils.arrayHelpers import getAttributeSet
-from geos.mesh.utils.arrayModifiers import (
- copyAttribute,
- createCellCenterAttribute
-)
+from geos.mesh.utils.arrayModifiers import ( copyAttribute, createCellCenterAttribute )
from geos.mesh.utils.multiblockHelpers import getBlockNames
-from geos.utils.GeosOutputsConstants import (
- GeosMeshOutputsEnum,
- GeosDomainNameEnum,
- getAttributeToTransferFromInitialTime
-)
+from geos.utils.GeosOutputsConstants import ( GeosMeshOutputsEnum, GeosDomainNameEnum, getAttributeToTransferFromInitialTime )
from geos.pv.utils.paraviewTreatments import getTimeStepIndex
+from geos.pv.utils.workflowFunctions import doExtractAndMerge
from vtkmodules.vtkCommonCore import ( vtkInformation, vtkInformationVector )
from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet
-from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
- VTKPythonAlgorithmBase, smdomain, 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 paraview.util.vtkAlgorithm import ( VTKPythonAlgorithmBase, smdomain, smproperty, smproxy ) # type: ignore[import-not-found]
+from paraview.detail.loghandler import ( VTKHandler ) # type: ignore[import-not-found]
__doc__ = """
PVGeosBlockExtractAndMerge is a Paraview plugin that:
@@ -112,8 +99,8 @@ def __init__( self: Self ) -> None:
outputType="vtkMultiBlockDataSet",
)
- self.extractFault: bool = False
- self.extractWell: bool = False
+ self.extractFault: bool = True
+ self.extractWell: bool = True
#: all time steps from input
self.m_timeSteps: npt.NDArray[ np.float64 ] = np.array( [] )
@@ -267,7 +254,6 @@ def RequestData(
int: 1 if calculation successfully ended, 0 otherwise.
"""
self.logger.info( f"Apply plugin { self.logger.name }." )
- print(self.m_requestDataStep)
try:
inputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
@@ -275,33 +261,31 @@ def RequestData(
executive = self.GetExecutive()
if self.m_requestDataStep == 0:
blockNames: list[ str ] = getBlockNames( inputMesh )
- if GeosDomainNameEnum.VOLUME_DOMAIN_NAME.value in blockNames:
- self.extractFault = True
+ if not GeosDomainNameEnum.VOLUME_DOMAIN_NAME.value in blockNames:
+ self.extractFault = False
- if GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
- self.extractWell = True
+ if not GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
+ self.extractWell = False
outputFaultsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
outputWellsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- self.doExtractAndMerge( inputMesh, self.outputCellsT0, outputFaultsT0, outputWellsT0 )
+ doExtractAndMerge( inputMesh, self.outputCellsT0, outputFaultsT0, outputWellsT0, self.extractFault, self.extractWell )
request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore
if self.m_requestDataStep == self.m_currentTimeStepIndex:
outputCells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
- outputFaults: vtkMultiBlockDataSet
- outputWells: vtkMultiBlockDataSet
+ outputFaults: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
+ outputWells: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
if self.extractFault:
outputFaults = self.GetOutputData( outInfoVec, 1 )
else:
- outputFaults = vtkMultiBlockDataSet()
outInfoVec.GetInformationObject( 1 ).Remove( vtkMultiBlockDataSet().DATA_OBJECT() )
if self.extractWell:
outputWells = self.GetOutputData( outInfoVec, 2 )
else:
- outputWells = vtkMultiBlockDataSet()
outInfoVec.GetInformationObject( 2 ).Remove( vtkMultiBlockDataSet().DATA_OBJECT() )
- self.doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells )
+ doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells, self.extractFault, self.extractWell )
# Copy attributes from the initial time step
meshAttributes: set[ str ] = getAttributeSet( self.outputCellsT0, False )
for ( attributeName, attributeNewName ) in getAttributeToTransferFromInitialTime().items():
@@ -322,62 +306,3 @@ def RequestData(
self.logger.critical( e, exc_info=True )
return 0
return 1
-
- def doExtractAndMerge(
- self: Self,
- mesh: vtkMultiBlockDataSet,
- outputCells: vtkMultiBlockDataSet,
- outputFaults: vtkMultiBlockDataSet,
- outputWells: vtkMultiBlockDataSet,
- ) -> None:
- """Apply block extraction and merge.
-
- Args:
- mesh (vtkMultiBlockDataSet): Mesh to process.
- outputCells (vtkMultiBlockDataSet): Output volume mesh.
- outputFaults (vtkMultiBlockDataSet): Output surface mesh.
- outputWells (vtkMultiBlockDataSet): Output well mesh.
- """
- # Extract blocks
- blockExtractor: GeosBlockExtractor = GeosBlockExtractor( mesh, extractFault=self.extractFault, extractWell=self.extractWell, speHandler=True )
- if not blockExtractor.logger.hasHandlers():
- blockExtractor.setLoggerHandler( VTKHandler() )
- blockExtractor.applyFilter()
-
- # recover output objects from GeosBlockExtractor filter and merge internal blocks
- volumeBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.volume
- outputCells.ShallowCopy( self.mergeBlocksFilter( volumeBlockExtracted, False ) )
- outputCells.Modified()
-
- if self.extractFault:
- faultBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.fault
- outputFaults.ShallowCopy( self.mergeBlocksFilter( faultBlockExtracted, True ) )
- outputFaults.Modified()
-
- if self.extractWell:
- wellBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.well
- outputWells.ShallowCopy( self.mergeBlocksFilter( wellBlockExtracted, False ) )
- outputWells.Modified()
-
- return
-
- def mergeBlocksFilter( self: Self,
- mesh: vtkMultiBlockDataSet,
- convertSurfaces: bool = False ) -> vtkMultiBlockDataSet:
- """Apply vtk merge block filter on input multi block mesh.
-
- Args:
- mesh (vtkMultiBlockDataSet): Mesh to merge.
- convertSurfaces (bool, optional): True to convert surface from vtkUnstructuredGrid to vtkPolyData.
- Defaults to False.
-
- Returns:
- vtkMultiBlockDataSet: Mesh composed of internal merged blocks.
- """
- mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( mesh, convertSurfaces, True )
- if not mergeBlockFilter.logger.hasHandlers():
- mergeBlockFilter.setLoggerHandler( VTKHandler() )
- mergeBlockFilter.applyFilter()
- mergedBlocks: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- mergedBlocks.ShallowCopy( mergeBlockFilter.getOutput() )
- return mergedBlocks
diff --git a/geos-pv/src/geos/pv/utils/workflowFunctions.py b/geos-pv/src/geos/pv/utils/workflowFunctions.py
new file mode 100644
index 000000000..7de604c3d
--- /dev/null
+++ b/geos-pv/src/geos/pv/utils/workflowFunctions.py
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
+# SPDX-FileContributor: Romain Baville
+# ruff: noqa: E402 # disable Module level import not at top of file
+
+
+from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
+from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
+
+from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet
+
+from paraview.detail.loghandler import ( VTKHandler ) # type: ignore[import-not-found]
+
+
+def doExtractAndMerge(
+ mesh: vtkMultiBlockDataSet,
+ outputCells: vtkMultiBlockDataSet,
+ outputFaults: vtkMultiBlockDataSet,
+ outputWells: vtkMultiBlockDataSet,
+ extractFault: bool,
+ extractWell: bool,
+) -> None:
+ """Apply block extraction and merge.
+
+ Args:
+ mesh (vtkMultiBlockDataSet): Mesh to process.
+ outputCells (vtkMultiBlockDataSet): Output volume mesh.
+ outputFaults (vtkMultiBlockDataSet): Output surface mesh.
+ outputWells (vtkMultiBlockDataSet): Output well mesh.
+ """
+ # Extract blocks
+ blockExtractor: GeosBlockExtractor = GeosBlockExtractor( mesh, extractFault=extractFault, extractWell=extractWell, speHandler=True )
+ if not blockExtractor.logger.hasHandlers():
+ blockExtractor.setLoggerHandler( VTKHandler() )
+ blockExtractor.applyFilter()
+
+ # recover output objects from GeosBlockExtractor filter and merge internal blocks
+ volumeBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.volume
+ outputCells.ShallowCopy( mergeBlocksFilter( volumeBlockExtracted, False ) )
+ outputCells.Modified()
+
+ if extractFault:
+ faultBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.fault
+ outputFaults.ShallowCopy( mergeBlocksFilter( faultBlockExtracted, False ) )
+ outputFaults.Modified()
+
+ if extractWell:
+ wellBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.well
+ outputWells.ShallowCopy( mergeBlocksFilter( wellBlockExtracted, False ) )
+ outputWells.Modified()
+
+ return
+
+def mergeBlocksFilter( mesh: vtkMultiBlockDataSet,
+ convertSurfaces: bool = False, ) -> vtkMultiBlockDataSet:
+ """Apply vtk merge block filter on input multi block mesh.
+
+ Args:
+ mesh (vtkMultiBlockDataSet): Mesh to merge.
+ convertSurfaces (bool, optional): True to convert surface from vtkUnstructuredGrid to vtkPolyData.
+ Defaults to False.
+
+ Returns:
+ vtkMultiBlockDataSet: Mesh composed of internal merged blocks.
+ """
+ mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( mesh, convertSurfaces, True )
+ if not mergeBlockFilter.logger.hasHandlers():
+ mergeBlockFilter.setLoggerHandler( VTKHandler() )
+ mergeBlockFilter.applyFilter()
+ mergedBlocks: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
+ mergedBlocks.ShallowCopy( mergeBlockFilter.getOutput() )
+ return mergedBlocks
From c8308dc3f351dad936249899fa9e06a109036b96 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Wed, 12 Nov 2025 13:43:15 +0100
Subject: [PATCH 45/62] fix executive
---
.../PVExtractMergeBlocksVolumeSurfaceWell.py | 19 ++++++++++---------
.../src/geos/pv/utils/workflowFunctions.py | 2 +-
2 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
index 7f27a49fb..16fa45c72 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
@@ -92,6 +92,7 @@ def __init__( self: Self ) -> None:
To apply in the case of output ".pvd" file contains Volume, Fault and
Well elements.
"""
+ print( "init")
super().__init__(
nInputPorts=1,
nOutputPorts=3,
@@ -147,6 +148,7 @@ def RequestInformation(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
+ print("RequestInformation")
executive = self.GetExecutive() # noqa: F841
outInfo = outInfoVec.GetInformationObject( 0 ) # noqa: F841
return 1
@@ -167,11 +169,11 @@ def RequestDataObject(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
+ print("RequestDataObject")
inData = self.GetInputData( inInfoVec, 0, 0 )
- outDataCells = self.GetOutputData( outInfoVec, 0 )
- outDataFaults = self.GetOutputData( outInfoVec, 1 )
- outDataWells = self.GetOutputData( outInfoVec, 2 )
assert inData is not None
+
+ outDataCells = self.GetOutputData( outInfoVec, 0 )
if outDataCells is None or ( not outDataCells.IsA( "vtkMultiBlockDataSet" ) ):
outDataCells = vtkMultiBlockDataSet()
outInfoVec.GetInformationObject( 0 ).Set(
@@ -179,6 +181,7 @@ def RequestDataObject(
outDataCells # type: ignore
)
+ outDataFaults = self.GetOutputData( outInfoVec, 1 )
if outDataFaults is None or ( not outDataFaults.IsA( "vtkMultiBlockDataSet" ) ):
outDataFaults = vtkMultiBlockDataSet()
outInfoVec.GetInformationObject( 1 ).Set(
@@ -186,6 +189,7 @@ def RequestDataObject(
outDataFaults # type: ignore
)
+ outDataWells = self.GetOutputData( outInfoVec, 2 )
if outDataWells is None or ( not outDataWells.IsA( "vtkMultiBlockDataSet" ) ):
outDataWells = vtkMultiBlockDataSet()
outInfoVec.GetInformationObject( 2 ).Set(
@@ -211,6 +215,7 @@ def RequestUpdateExtent(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
+ print("RequestUpdateExtent")
executive = self.GetExecutive()
inInfo = inInfoVec[ 0 ]
# get displayed time step info before updating time
@@ -253,6 +258,7 @@ def RequestData(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
+ print("RequestData")
self.logger.info( f"Apply plugin { self.logger.name }." )
try:
inputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
@@ -261,9 +267,8 @@ def RequestData(
executive = self.GetExecutive()
if self.m_requestDataStep == 0:
blockNames: list[ str ] = getBlockNames( inputMesh )
- if not GeosDomainNameEnum.VOLUME_DOMAIN_NAME.value in blockNames:
+ if not GeosDomainNameEnum.FAULT_DOMAIN_NAME.value in blockNames:
self.extractFault = False
-
if not GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
self.extractWell = False
@@ -277,13 +282,9 @@ def RequestData(
outputWells: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
if self.extractFault:
outputFaults = self.GetOutputData( outInfoVec, 1 )
- else:
- outInfoVec.GetInformationObject( 1 ).Remove( vtkMultiBlockDataSet().DATA_OBJECT() )
if self.extractWell:
outputWells = self.GetOutputData( outInfoVec, 2 )
- else:
- outInfoVec.GetInformationObject( 2 ).Remove( vtkMultiBlockDataSet().DATA_OBJECT() )
doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells, self.extractFault, self.extractWell )
# Copy attributes from the initial time step
diff --git a/geos-pv/src/geos/pv/utils/workflowFunctions.py b/geos-pv/src/geos/pv/utils/workflowFunctions.py
index 7de604c3d..39ae35ee7 100644
--- a/geos-pv/src/geos/pv/utils/workflowFunctions.py
+++ b/geos-pv/src/geos/pv/utils/workflowFunctions.py
@@ -41,7 +41,7 @@ def doExtractAndMerge(
if extractFault:
faultBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.fault
- outputFaults.ShallowCopy( mergeBlocksFilter( faultBlockExtracted, False ) )
+ outputFaults.ShallowCopy( mergeBlocksFilter( faultBlockExtracted, True ) )
outputFaults.Modified()
if extractWell:
From 3cc2be7660d033ec52d6c17e740a7914a21c0b75 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Wed, 12 Nov 2025 17:10:30 +0100
Subject: [PATCH 46/62] Clean the plugin outputs messages
---
.../PVExtractMergeBlocksVolumeSurfaceWell.py | 215 +++++++++---------
1 file changed, 104 insertions(+), 111 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
index 16fa45c72..01dd30c7f 100644
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
@@ -21,7 +21,8 @@
from geos.mesh.utils.arrayModifiers import ( copyAttribute, createCellCenterAttribute )
from geos.mesh.utils.multiblockHelpers import getBlockNames
-from geos.utils.GeosOutputsConstants import ( GeosMeshOutputsEnum, GeosDomainNameEnum, getAttributeToTransferFromInitialTime )
+from geos.utils.GeosOutputsConstants import ( GeosMeshOutputsEnum, GeosDomainNameEnum,
+ getAttributeToTransferFromInitialTime )
from geos.pv.utils.paraviewTreatments import getTimeStepIndex
from geos.pv.utils.workflowFunctions import doExtractAndMerge
@@ -29,8 +30,10 @@
from vtkmodules.vtkCommonCore import ( vtkInformation, vtkInformationVector )
from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet
-from paraview.util.vtkAlgorithm import ( VTKPythonAlgorithmBase, smdomain, smproperty, smproxy ) # type: ignore[import-not-found]
-from paraview.detail.loghandler import ( VTKHandler ) # type: ignore[import-not-found]
+from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
+ VTKPythonAlgorithmBase, smdomain, smproperty, smproxy )
+from paraview.detail.loghandler import ( # type: ignore[import-not-found]
+ VTKHandler )
__doc__ = """
PVGeosBlockExtractAndMerge is a Paraview plugin that:
@@ -89,10 +92,9 @@ class PVGeosBlockExtractAndMerge( VTKPythonAlgorithmBase ):
def __init__( self: Self ) -> None:
"""Paraview plugin to extract and merge ranks from Geos output Mesh.
- To apply in the case of output ".pvd" file contains Volume, Fault and
+ To apply in the case of output ".pvd" file contains Volume, Fault or
Well elements.
"""
- print( "init")
super().__init__(
nInputPorts=1,
nOutputPorts=3,
@@ -103,14 +105,12 @@ def __init__( self: Self ) -> None:
self.extractFault: bool = True
self.extractWell: bool = True
- #: all time steps from input
- self.m_timeSteps: npt.NDArray[ np.float64 ] = np.array( [] )
- #: displayed time step in the IHM
- self.m_currentTime: float = 0.0
- #: time step index of displayed time step
- self.m_currentTimeStepIndex: int = 0
- #: request data processing step - incremented each time RequestUpdateExtent is called
- self.m_requestDataStep: int = -1
+ # All time steps from input
+ self.timeSteps: npt.NDArray[ np.float64 ] = np.array( [] )
+ # The time step of the input when the plugin is called or updated
+ self.currentTimeStepIndex: int = 0
+ # The time step studies. It is incremental -1 during the initialization, from 0 to self.currentTimeStepIndex during the computation and -2 at the end of the computation
+ self.requestDataStep: int = -1
self.outputCellsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
@@ -118,27 +118,13 @@ def __init__( self: Self ) -> None:
self.logger.setLevel( logging.INFO )
self.logger.addHandler( VTKHandler() )
- 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(), "vtkMultiBlockDataSet" )
- return 1
-
- def RequestInformation(
+ def RequestDataObject(
self: Self,
- request: vtkInformation, # noqa: F841
- inInfoVec: list[ vtkInformationVector ], # noqa: F841
+ request: vtkInformation,
+ inInfoVec: list[ vtkInformationVector ],
outInfoVec: vtkInformationVector,
) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestInformation.
+ """Inherited from VTKPythonAlgorithmBase::RequestDataObject.
Args:
request (vtkInformation): request
@@ -148,18 +134,33 @@ def RequestInformation(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
- print("RequestInformation")
- executive = self.GetExecutive() # noqa: F841
- outInfo = outInfoVec.GetInformationObject( 0 ) # noqa: F841
- return 1
+ inData = self.GetInputData( inInfoVec, 0, 0 )
+ assert inData is not None
- def RequestDataObject(
+ outDataCells = self.GetOutputData( outInfoVec, 0 )
+ if outDataCells is None or ( not outDataCells.IsA( "vtkMultiBlockDataSet" ) ):
+ outDataCells = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 0 ).Set( outDataCells.DATA_OBJECT(), outDataCells ) # type: ignore
+
+ outDataFaults = self.GetOutputData( outInfoVec, 1 )
+ if outDataFaults is None or ( not outDataFaults.IsA( "vtkMultiBlockDataSet" ) ):
+ outDataFaults = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 1 ).Set( outDataFaults.DATA_OBJECT(), outDataFaults ) # type: ignore
+
+ outDataWells = self.GetOutputData( outInfoVec, 2 )
+ if outDataWells is None or ( not outDataWells.IsA( "vtkMultiBlockDataSet" ) ):
+ outDataWells = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 2 ).Set( outDataWells.DATA_OBJECT(), outDataWells ) # type: ignore
+
+ return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return]
+
+ def RequestInformation(
self: Self,
request: vtkInformation,
inInfoVec: list[ vtkInformationVector ],
outInfoVec: vtkInformationVector,
) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestDataObject.
+ """Inherited from VTKPythonAlgorithmBase::RequestInformation.
Args:
request (vtkInformation): request
@@ -169,44 +170,45 @@ def RequestDataObject(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
- print("RequestDataObject")
- inData = self.GetInputData( inInfoVec, 0, 0 )
- assert inData is not None
+ self.logger.info( f"Apply plugin { self.logger.name }." )
- outDataCells = self.GetOutputData( outInfoVec, 0 )
- if outDataCells is None or ( not outDataCells.IsA( "vtkMultiBlockDataSet" ) ):
- outDataCells = vtkMultiBlockDataSet()
- outInfoVec.GetInformationObject( 0 ).Set(
- outDataCells.DATA_OBJECT(),
- outDataCells # type: ignore
- )
+ executive = self.GetExecutive()
+ inInfo = inInfoVec[ 0 ]
+ self.timeSteps = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() )
- outDataFaults = self.GetOutputData( outInfoVec, 1 )
- if outDataFaults is None or ( not outDataFaults.IsA( "vtkMultiBlockDataSet" ) ):
- outDataFaults = vtkMultiBlockDataSet()
- outInfoVec.GetInformationObject( 1 ).Set(
- outDataFaults.DATA_OBJECT(),
- outDataFaults # type: ignore
+ # The time of the input mesh
+ currentTime = inInfo.GetInformationObject( 0 ).Get( executive.UPDATE_TIME_STEP() )
+
+ self.currentTimeStepIndex = getTimeStepIndex( currentTime, self.timeSteps )
+
+ inputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfo )
+ blockNames: list[ str ] = getBlockNames( inputMesh )
+ if GeosDomainNameEnum.FAULT_DOMAIN_NAME.value not in blockNames:
+ self.extractFault = False
+ self.logger.warning(
+ f"The mesh to process does not contains the block named { GeosDomainNameEnum.FAULT_DOMAIN_NAME.value }. The output 'Fault' will be an empty mesh."
)
- outDataWells = self.GetOutputData( outInfoVec, 2 )
- if outDataWells is None or ( not outDataWells.IsA( "vtkMultiBlockDataSet" ) ):
- outDataWells = vtkMultiBlockDataSet()
- outInfoVec.GetInformationObject( 2 ).Set(
- outDataWells.DATA_OBJECT(),
- outDataWells # type: ignore
+ if GeosDomainNameEnum.WELL_DOMAIN_NAME.value not in blockNames:
+ self.extractWell = False
+ self.logger.warning(
+ f"The mesh to process does not contains the block named { GeosDomainNameEnum.WELL_DOMAIN_NAME.value }. The output 'Well' will be an empty mesh."
)
- return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return]
+ return 1
def RequestUpdateExtent(
self: Self,
- request: vtkInformation, # noqa: F841
+ request: vtkInformation,
inInfoVec: list[ vtkInformationVector ],
outInfoVec: vtkInformationVector,
) -> int:
"""Inherited from VTKPythonAlgorithmBase::RequestUpdateExtent.
+ This function is call at each change of time:
+ on Paraview with the widget Time
+ if request.Set( self.GetExecutive.CONTINUE_EXECUTING(), 1 ) is set (time steps iterator)
+
Args:
request (vtkInformation): request
inInfoVec (list[vtkInformationVector]): input objects
@@ -215,31 +217,22 @@ def RequestUpdateExtent(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
- print("RequestUpdateExtent")
executive = self.GetExecutive()
inInfo = inInfoVec[ 0 ]
- # get displayed time step info before updating time
- if self.m_requestDataStep == -1:
- self.m_timeSteps = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() # type: ignore
- )
- self.m_currentTime = inInfo.GetInformationObject( 0 ).Get( executive.UPDATE_TIME_STEP() # type: ignore
- )
- self.m_currentTimeStepIndex = getTimeStepIndex( self.m_currentTime, self.m_timeSteps )
- # update requestDataStep
- self.m_requestDataStep += 1
-
- # update time according to requestDataStep iterator
- inInfo.GetInformationObject( 0 ).Set(
- executive.UPDATE_TIME_STEP(),
- self.m_timeSteps[ self.m_requestDataStep ] # type: ignore
- )
- outInfoVec.GetInformationObject( 0 ).Set(
- executive.UPDATE_TIME_STEP(),
- self.m_timeSteps[ self.m_requestDataStep ] # type: ignore
- )
- # update all objects according to new time info
- self.Modified()
+ # Get update time step from Paraview
+ if self.requestDataStep == -2:
+ currentTime = inInfo.GetInformationObject( 0 ).Get( executive.UPDATE_TIME_STEP() )
+ self.currentTimeStepIndex = getTimeStepIndex( currentTime, self.timeSteps )
+ self.requestDataStep = self.currentTimeStepIndex
+
+ # Update requestDataStep
+ else:
+ self.requestDataStep += 1
+
+ # Update time according to requestDataStep iterator
+ inInfo.GetInformationObject( 0 ).Set( executive.UPDATE_TIME_STEP(), self.timeSteps[ self.requestDataStep ] )
+
return 1
def RequestData(
@@ -258,46 +251,46 @@ def RequestData(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
- print("RequestData")
- self.logger.info( f"Apply plugin { self.logger.name }." )
try:
inputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
-
- # Time controller, only the first and the current time step are computed
executive = self.GetExecutive()
- if self.m_requestDataStep == 0:
- blockNames: list[ str ] = getBlockNames( inputMesh )
- if not GeosDomainNameEnum.FAULT_DOMAIN_NAME.value in blockNames:
- self.extractFault = False
- if not GeosDomainNameEnum.WELL_DOMAIN_NAME.value in blockNames:
- self.extractWell = False
-
- outputFaultsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- outputWellsT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- doExtractAndMerge( inputMesh, self.outputCellsT0, outputFaultsT0, outputWellsT0, self.extractFault, self.extractWell )
- request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore
- if self.m_requestDataStep == self.m_currentTimeStepIndex:
- outputCells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
- outputFaults: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- outputWells: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- if self.extractFault:
- outputFaults = self.GetOutputData( outInfoVec, 1 )
- if self.extractWell:
- outputWells = self.GetOutputData( outInfoVec, 2 )
+ # First time step, compute the initial properties (useful for geomechanics analyses)
+ if self.requestDataStep == 0:
+ self.logger.info( "Apply the plugin for the first time step to get the initial properties." )
+ doExtractAndMerge( inputMesh, self.outputCellsT0, vtkMultiBlockDataSet(), vtkMultiBlockDataSet(),
+ self.extractFault, self.extractWell )
+ request.Set( executive.CONTINUE_EXECUTING(), 1 )
+
+ # Current time step, extract, merge, rename and transfer properties
+ if self.requestDataStep == self.currentTimeStepIndex:
+ self.logger.info( f"Apply the filter for the current time step: { self.currentTimeStepIndex }." )
+ outputCells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
+ outputFaults: vtkMultiBlockDataSet = self.GetOutputData(
+ outInfoVec, 1 ) if self.extractFault else vtkMultiBlockDataSet()
+ outputWells: vtkMultiBlockDataSet = self.GetOutputData(
+ outInfoVec, 2 ) if self.extractWell else vtkMultiBlockDataSet()
+ doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells, self.extractFault,
+ self.extractWell )
- doExtractAndMerge( inputMesh, outputCells, outputFaults, outputWells, self.extractFault, self.extractWell )
# Copy attributes from the initial time step
meshAttributes: set[ str ] = getAttributeSet( self.outputCellsT0, False )
for ( attributeName, attributeNewName ) in getAttributeToTransferFromInitialTime().items():
if attributeName in meshAttributes:
- copyAttribute( self.outputCellsT0, outputCells, attributeName, attributeNewName, False, self.logger )
+ copyAttribute( self.outputCellsT0, outputCells, attributeName, attributeNewName, False,
+ self.logger )
+
# Create elementCenter attribute in the volume mesh if needed
cellCenterAttributeName: str = GeosMeshOutputsEnum.ELEMENT_CENTER.attributeName
createCellCenterAttribute( outputCells, cellCenterAttributeName )
- # Stop the computation
- request.Remove( executive.CONTINUE_EXECUTING() ) # type: ignore
- self.m_requestDataStep = -1
+
+ # Stop the time step iteration
+ request.Remove( executive.CONTINUE_EXECUTING() )
+
+ # Set to -2 in case time changes on Paraview
+ self.requestDataStep = -2
+
+ self.logger.info( f"The plugin { self.logger.name } succeeded." )
except AssertionError as e:
self.logger.error( f"The plugin failed.\n{e}" )
return 0
From 62cc57d03e2417554690d0e28a712de2e3964dfd Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Wed, 12 Nov 2025 17:12:29 +0100
Subject: [PATCH 47/62] Rename the plugin file
---
...geBlocksVolumeSurfaceWell.py => PVGeosBlockExtractAndMerge.py} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename geos-pv/src/geos/pv/plugins/{PVExtractMergeBlocksVolumeSurfaceWell.py => PVGeosBlockExtractAndMerge.py} (100%)
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
similarity index 100%
rename from geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurfaceWell.py
rename to geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
From dddfed1717fd2f156a3461e3b9f32d961d5db31e Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Wed, 12 Nov 2025 17:22:32 +0100
Subject: [PATCH 48/62] Remove old PVExtractAndMerge plugin
---
docs/geos_pv_docs/processing.rst | 24 +-
docs/geos_pv_docs/utils.rst | 8 +
.../pv/plugins/PVExtractMergeBlocksVolume.py | 315 ----------------
.../PVExtractMergeBlocksVolumeSurface.py | 337 -----------------
.../plugins/PVExtractMergeBlocksVolumeWell.py | 346 ------------------
.../plugins/PVGeomechanicsWorkflowVolume.py | 6 +-
.../PVGeomechanicsWorkflowVolumeSurface.py | 7 +-
...PVGeomechanicsWorkflowVolumeSurfaceWell.py | 7 +-
.../PVGeomechanicsWorkflowVolumeWell.py | 7 +-
9 files changed, 23 insertions(+), 1034 deletions(-)
delete mode 100644 geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
delete mode 100644 geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py
delete mode 100644 geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py
diff --git a/docs/geos_pv_docs/processing.rst b/docs/geos_pv_docs/processing.rst
index 726100fd0..368009192 100644
--- a/docs/geos_pv_docs/processing.rst
+++ b/docs/geos_pv_docs/processing.rst
@@ -47,28 +47,10 @@ PVClipToMainFrame
Geos output pre-processing
+++++++++++++++++++++++++++++
-PVExtractMergeBlocksVolume plugin
--------------------------------------------
-
-.. automodule:: geos.pv.plugins.PVExtractMergeBlocksVolume
-
-
-PVExtractMergeBlocksVolumeSurface plugin
---------------------------------------------------
-
-.. automodule:: geos.pv.plugins.PVExtractMergeBlocksVolumeSurface
-
-
-PVExtractMergeBlocksVolumeSurfaceWell plugin
-------------------------------------------------------
-
-.. automodule:: geos.pv.plugins.PVExtractMergeBlocksVolumeSurfaceWell
-
-
-PVExtractMergeBlocksVolumeWell plugin
------------------------------------------------
+PVGeosBlockExtractAndMerge plugin
+----------------------------------
-.. automodule:: geos.pv.plugins.PVExtractMergeBlocksVolumeWell
+.. automodule:: geos.pv.plugins.PVGeosBlockExtractAndMerge
diff --git a/docs/geos_pv_docs/utils.rst b/docs/geos_pv_docs/utils.rst
index 3a868de54..31443918f 100644
--- a/docs/geos_pv_docs/utils.rst
+++ b/docs/geos_pv_docs/utils.rst
@@ -24,6 +24,14 @@ geos.pv.utils.paraviewTreatments module
---------------------------------------------
.. automodule:: geos.pv.utils.paraviewTreatments
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+geos.pv.utils.workflowFunctions module
+---------------------------------------
+
+.. automodule:: geos.pv.utils.workflowFunctions
:members:
:undoc-members:
:show-inheritance:
\ No newline at end of file
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
deleted file mode 100644
index 382f8bc87..000000000
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolume.py
+++ /dev/null
@@ -1,315 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0
-# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Martin Lemay
-# ruff: noqa: E402 # disable Module level import not at top of file
-import sys
-from pathlib import Path
-import numpy as np
-import numpy.typing as npt
-from typing_extensions import Self
-from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector
-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
-
-update_paths()
-
-from geos.utils.GeosOutputsConstants import (
- GeosMeshOutputsEnum,
- getAttributeToTransferFromInitialTime,
-)
-from geos.utils.Logger import ERROR, INFO, Logger, getLogger
-from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
-from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
-from geos.mesh.utils.arrayModifiers import (
- copyAttribute,
- createCellCenterAttribute,
-)
-from geos.pv.utils.paraviewTreatments import getTimeStepIndex
-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
-
-__doc__ = """
-PVExtractMergeBlocksVolume is a Paraview plugin that allows to merge ranks
-of a Geos output objects containing only a volumic mesh.
-
-Input and output types are vtkMultiBlockDataSet.
-
-This filter results in a single output pipeline that contains the volume mesh.
-If multiple regions were defined in the volume mesh, they are preserved as
-distinct blocks.
-
-To use it:
-
-* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVExtractMergeBlocksVolume.
-* Select the Geos output .pvd file loaded in Paraview.
-* Search and Apply PVExtractMergeBlocksVolume Filter.
-
-"""
-
-
-@smproxy.filter(
- name="PVExtractMergeBlocksVolume",
- label="Geos Extract And Merge Blocks - Volume Only",
-)
-@smhint.xml( """
-
-
- """ )
-@smproperty.input( name="Input", port_index=0 )
-@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
-class PVExtractMergeBlocksVolume( VTKPythonAlgorithmBase ):
-
- def __init__( self: Self ) -> None:
- """Paraview plugin to extract and merge ranks from Geos output Mesh.
-
- To apply in the case of output ".pvd" file contains only a Volume.
- """
- super().__init__(
- nInputPorts=1,
- nOutputPorts=1,
- inputType="vtkMultiBlockDataSet",
- outputType="vtkMultiBlockDataSet",
- )
-
- #: all time steps from input
- self.m_timeSteps: npt.NDArray[ np.float64 ] = np.array( [] )
- #: displayed time step in the IHM
- self.m_currentTime: float = 0.0
- #: time step index of displayed time step
- self.m_currentTimeStepIndex: int = 0
- #: request data processing step - incremented each time RequestUpdateExtent is called
- self.m_requestDataStep: int = -1
-
- #: saved object at initial time step
- self.m_outputT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
-
- #: set logger
- self.m_logger: Logger = getLogger( "Extract and merge block Filter" )
-
- def SetLogger( self: Self, logger: Logger ) -> None:
- """Set filter logger.
-
- Args:
- logger (Logger): logger
- """
- self.m_logger = logger
-
- 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(), "vtkMultiBlockDataSet" )
- return 1
-
- 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,
- 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
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- inData = self.GetInputData( inInfoVec, 0, 0 )
- outData = self.GetOutputData( outInfoVec, 0 )
- 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[no-any-return]
-
- def RequestUpdateExtent(
- self: Self,
- request: vtkInformation, # noqa: F841
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestUpdateExtent.
-
- 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()
- inInfo = inInfoVec[ 0 ]
- # get displayed time step info before updating time
- if self.m_requestDataStep == -1:
- self.m_logger.info( f"Apply filter {__name__}" )
- self.m_timeSteps = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() # type: ignore
- )
- self.m_currentTime = inInfo.GetInformationObject( 0 ).Get( executive.UPDATE_TIME_STEP() # type: ignore
- )
- self.m_currentTimeStepIndex = getTimeStepIndex( self.m_currentTime, self.m_timeSteps )
- # update requestDataStep
- self.m_requestDataStep += 1
-
- # update time according to requestDataStep iterator
- inInfo.GetInformationObject( 0 ).Set(
- executive.UPDATE_TIME_STEP(),
- self.m_timeSteps[ self.m_requestDataStep ] # type: ignore
- )
- outInfoVec.GetInformationObject( 0 ).Set(
- executive.UPDATE_TIME_STEP(),
- self.m_timeSteps[ self.m_requestDataStep ] # type: ignore
- )
-
- # update all objects according to new time info
- self.Modified()
- return 1
-
- def RequestData(
- self: Self,
- request: vtkInformation,
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestData.
-
- Args:
- request (vtkInformation): request
- inInfoVec (list[vtkInformationVector]): input objects
- outInfoVec (vtkInformationVector): output objects
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- try:
- input: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
- output: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
-
- assert input is not None, "Input object is null."
- assert output is not None, "Output object is null."
-
- # time controller
- executive = self.GetExecutive()
- if self.m_requestDataStep == 0:
- # first time step
- # do extraction and merge (do not display phase info)
- self.m_logger.setLevel( ERROR )
- self.doExtractAndMerge( input, output )
- self.m_logger.setLevel( INFO )
- # save input mesh to copy later
- self.m_outputT0.ShallowCopy( output )
- request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore
- if self.m_requestDataStep >= self.m_currentTimeStepIndex:
- # displayed time step, no need to go further
- request.Remove( executive.CONTINUE_EXECUTING() ) # type: ignore
- # reinitialize requestDataStep if filter is recalled later
- self.m_requestDataStep = -1
- # do extraction and merge
- self.doExtractAndMerge( input, output )
- # copy attributes from initial time step
- for (
- attributeName,
- attributeNewName,
- ) in getAttributeToTransferFromInitialTime().items():
- copyAttribute( self.m_outputT0, output, attributeName, attributeNewName )
- # create elementCenter attribute if needed
- cellCenterAttributeName: str = ( GeosMeshOutputsEnum.ELEMENT_CENTER.attributeName )
- createCellCenterAttribute( output, cellCenterAttributeName )
-
- # TODO add ForceStaticMesh filter https://gitlab.kitware.com/paraview/plugins/staticmeshplugin
-
- except AssertionError as e:
- mess: str = "Block extraction and merge failed due to:"
- self.m_logger.error( mess )
- self.m_logger.error( str( e ) )
- return 0
- except Exception as e:
- mess1: str = "Block extraction and merge failed due to:"
- self.m_logger.critical( mess1 )
- self.m_logger.critical( e, exc_info=True )
- return 0
-
- return 1
-
- def doExtractAndMerge( self: Self, input: vtkMultiBlockDataSet, output: vtkMultiBlockDataSet ) -> bool:
- """Apply block extraction and merge.
-
- Args:
- input (vtkMultiBlockDataSet): input multi block
- output (vtkMultiBlockDataSet): output volume mesh
-
- Returns:
- bool: True if extraction and merge successfully ended, False otherwise
- """
- # extract blocks
- blockExtractor: GeosBlockExtractor = GeosBlockExtractor( input )
- blockExtractor.applyFilter()
-
- # recover output objects from GeosBlockExtractor filter
- volumeBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.volume
- assert volumeBlockExtracted is not None, "Extracted Volume mesh is null."
-
- # merge internal blocks
- output.ShallowCopy( self.mergeBlocksFilter( volumeBlockExtracted, False ) )
- output.Modified()
- self.m_logger.info( "Ranks were successfully merged together." )
- return True
-
- def mergeBlocksFilter( self: Self,
- input: vtkMultiBlockDataSet,
- convertSurfaces: bool = False ) -> vtkMultiBlockDataSet:
- """Apply vtk merge block filter on input multi block mesh.
-
- Args:
- input (vtkMultiBlockDataSet): multiblock mesh to merge
- convertSurfaces (bool, optional): True to convert surface from vtkUnstructuredGrid to
- vtkPolyData. Defaults to False.
-
- Returns:
- vtkMultiBlockDataSet: Multiblock mesh composed of internal merged blocks.
- """
- mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( input, convertSurfaces, True )
- if not mergeBlockFilter.logger.hasHandlers():
- mergeBlockFilter.setLoggerHandler( VTKHandler() )
- mergeBlockFilter.applyFilter()
- mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
- assert mergedBlocks is not None, "Final merged MultiBlockDataSet is null."
- return mergedBlocks
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py
deleted file mode 100644
index a670df2b5..000000000
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeSurface.py
+++ /dev/null
@@ -1,337 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0
-# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Martin Lemay
-# ruff: noqa: E402 # disable Module level import not at top of file
-import sys
-from pathlib import Path
-
-import numpy as np
-import numpy.typing as npt
-from typing_extensions import Self
-from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector
-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
-
-update_paths()
-
-from geos.utils.GeosOutputsConstants import (
- GeosMeshOutputsEnum,
- getAttributeToTransferFromInitialTime,
-)
-from geos.utils.Logger import ERROR, INFO, Logger, getLogger
-from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
-from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
-from geos.mesh.utils.arrayModifiers import (
- copyAttribute,
- createCellCenterAttribute,
-)
-from geos.pv.utils.paraviewTreatments import getTimeStepIndex
-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
-
-__doc__ = """
-PVExtractMergeBlocksVolumeSurface is a Paraview plugin that allows to merge
-ranks of a Geos output objects containing a volumic mesh and surfaces.
-
-Input and output types are vtkMultiBlockDataSet.
-
-This filter results in 2 output pipelines:
-
-* first pipeline contains the volume mesh. If multiple regions were defined in
- the volume mesh, they are preserved as distinct blocks.
-* second pipeline contains surfaces. If multiple surfaces were used, they are
- preserved as distinct blocks.
-
-To use it:
-
-* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVExtractMergeBlocksVolumeSurface.
-* Select the Geos output .pvd file loaded in Paraview.
-* Search and Apply PVExtractMergeBlocksVolumeSurface Filter.
-
-"""
-
-
-@smproxy.filter(
- name="PVExtractMergeBlocksVolumeSurface",
- label="Geos Extract And Merge Blocks - Volume/Surface",
-)
-@smhint.xml( """
-
-
-
- """ )
-@smproperty.input( name="Input", port_index=0 )
-@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
-class PVExtractMergeBlocksVolumeSurface( VTKPythonAlgorithmBase ):
-
- def __init__( self: Self ) -> None:
- """Paraview plugin to extract and merge ranks from Geos output Mesh.
-
- To apply in the case of output ".pvd" file contains Volume and Fault
- elements.
- """
- super().__init__(
- nInputPorts=1,
- nOutputPorts=2,
- inputType="vtkMultiBlockDataSet",
- outputType="vtkMultiBlockDataSet",
- )
-
- #: all time steps from input
- self.m_timeSteps: npt.NDArray[ np.float64 ] = np.array( [] )
- #: displayed time step in the IHM
- self.m_currentTime: float = 0.0
- #: time step index of displayed time step
- self.m_currentTimeStepIndex: int = 0
- #: request data processing step - incremented each time RequestUpdateExtent is called
- self.m_requestDataStep: int = -1
-
- # saved object at initial time step
- self.m_outputT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
-
- # set logger
- self.m_logger: Logger = getLogger( "Extract and merge block Filter" )
-
- def SetLogger( self: Self, logger: Logger ) -> None:
- """Set filter logger.
-
- Args:
- logger (Logger): logger
- """
- self.m_logger = logger
-
- 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(), "vtkMultiBlockDataSet" )
- return 1
-
- 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,
- 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
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- inData = self.GetInputData( inInfoVec, 0, 0 )
- outDataCells = self.GetOutputData( outInfoVec, 0 )
- outDataFaults = self.GetOutputData( outInfoVec, 1 )
- assert inData is not None
- if outDataCells is None or ( not outDataCells.IsA( "vtkMultiBlockDataSet" ) ):
- outDataCells = vtkMultiBlockDataSet()
- outInfoVec.GetInformationObject( 0 ).Set( outDataCells.DATA_OBJECT(), outDataCells ) # type: ignore
- if outDataFaults is None or ( not outDataFaults.IsA( "vtkMultiBlockDataSet" ) ):
- outDataFaults = vtkMultiBlockDataSet()
- outInfoVec.GetInformationObject( 1 ).Set( outDataFaults.DATA_OBJECT(), outDataFaults ) # type: ignore
-
- return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return]
-
- def RequestUpdateExtent(
- self: Self,
- request: vtkInformation, # noqa: F841
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestUpdateExtent.
-
- 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()
- inInfo = inInfoVec[ 0 ]
- # get displayed time step info before updating time
- if self.m_requestDataStep == -1:
- self.m_logger.info( f"Apply filter {__name__}" )
- self.m_timeSteps = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() ) # type: ignore
- self.m_currentTime = inInfo.GetInformationObject( 0 ).Get( executive.UPDATE_TIME_STEP() ) # type: ignore
- self.m_currentTimeStepIndex = getTimeStepIndex( self.m_currentTime, self.m_timeSteps )
- # update requestDataStep
- self.m_requestDataStep += 1
-
- # update time according to requestDataStep iterator
- inInfo.GetInformationObject( 0 ).Set(
- executive.UPDATE_TIME_STEP(), # type: ignore
- self.m_timeSteps[ self.m_requestDataStep ],
- )
- outInfoVec.GetInformationObject( 0 ).Set(
- executive.UPDATE_TIME_STEP(), # type: ignore
- self.m_timeSteps[ self.m_requestDataStep ],
- )
-
- # update all objects according to new time info
- self.Modified()
- return 1
-
- def RequestData(
- self: Self,
- request: vtkInformation,
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestData.
-
- Args:
- request (vtkInformation): request
- inInfoVec (list[vtkInformationVector]): input objects
- outInfoVec (vtkInformationVector): output objects
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- try:
- input: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
- outputCells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
- outputFaults: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 1 )
-
- assert input is not None, "Input MultiBlockDataSet is null."
- assert outputCells is not None, "Output volum mesh is null."
- assert outputFaults is not None, "Output surface mesh is null."
-
- # time controller
- executive = self.GetExecutive()
- if self.m_requestDataStep == 0:
- # first time step
- # do extraction and merge (do not display phase info)
- outputFaults0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- self.m_logger.setLevel( ERROR )
- self.doExtractAndMerge( input, self.m_outputT0, outputFaults0 )
- self.m_logger.setLevel( INFO )
- request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore
-
- if self.m_requestDataStep >= self.m_currentTimeStepIndex:
- # displayed time step, no need to go further
- request.Remove( executive.CONTINUE_EXECUTING() ) # type: ignore
- # reinitialize requestDataStep if filter is recalled later
- self.m_requestDataStep = -1
- # do extraction and merge
- self.doExtractAndMerge( input, outputCells, outputFaults )
- # copy attributes from initial time step
- for (
- attributeName,
- attributeNewName,
- ) in getAttributeToTransferFromInitialTime().items():
- copyAttribute( self.m_outputT0, outputCells, attributeName, attributeNewName )
- # create elementCenter attribute if needed
- cellCenterAttributeName: str = ( GeosMeshOutputsEnum.ELEMENT_CENTER.attributeName )
- createCellCenterAttribute( outputCells, cellCenterAttributeName )
-
- except AssertionError as e:
- mess: str = "Block extraction and merge failed due to:"
- self.m_logger.error( mess )
- self.m_logger.error( str( e ) )
- return 0
- except Exception as e:
- mess1: str = "Block extraction and merge failed due to:"
- self.m_logger.critical( mess1 )
- self.m_logger.critical( e, exc_info=True )
- return 0
-
- return 1
-
- def doExtractAndMerge(
- self: Self,
- input: vtkMultiBlockDataSet,
- outputCells: vtkMultiBlockDataSet,
- outputFaults: vtkMultiBlockDataSet,
- ) -> bool:
- """Apply block extraction and merge.
-
- Args:
- input (vtkMultiBlockDataSet): input multi block
- outputCells (vtkMultiBlockDataSet): output volume mesh
- outputFaults (vtkMultiBlockDataSet): output surface mesh
-
- Returns:
- bool: True if extraction and merge successfully eneded, False otherwise
- """
- # extract blocks
- blockExtractor: GeosBlockExtractor = GeosBlockExtractor( input, extractFault=True )
- blockExtractor.applyFilter()
-
- # recover output objects from GeosBlockExtractor filter
- volumeBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.volume
- faultBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.fault
-
- # rename attributes and merge blocks
- assert volumeBlockExtracted is not None, "Extracted Volume mesh is null."
- outputCells.ShallowCopy( self.mergeBlocksFilter( volumeBlockExtracted, False ) )
- outputCells.Modified()
-
- assert faultBlockExtracted is not None, "Extracted Fault mesh is null."
- outputFaults.ShallowCopy( self.mergeBlocksFilter( faultBlockExtracted, True ) )
- outputFaults.Modified()
-
- self.m_logger.info( "Volume blocks were successfully splitted from surfaces" +
- " and ranks were merged together." )
- return True
-
- def mergeBlocksFilter( self: Self,
- input: vtkMultiBlockDataSet,
- convertSurfaces: bool = False ) -> vtkMultiBlockDataSet:
- """Apply vtk merge block filter on input multi block mesh.
-
- Args:
- input (vtkMultiBlockDataSet): multiblock mesh to merge
- convertSurfaces (bool, optional): True to convert surface from vtkUnstructuredGrid to
- vtkPolyData. Defaults to False.
-
- Returns:
- vtkMultiBlockDataSet: Multiblock mesh composed of internal merged blocks.
- """
- mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( input, convertSurfaces, True )
- if not mergeBlockFilter.logger.hasHandlers():
- mergeBlockFilter.setLoggerHandler( VTKHandler() )
- mergeBlockFilter.applyFilter()
- mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
- assert mergedBlocks is not None, "Final merged MultiBlockDataSet is null."
- return mergedBlocks
diff --git a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py b/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py
deleted file mode 100644
index d1da166b9..000000000
--- a/geos-pv/src/geos/pv/plugins/PVExtractMergeBlocksVolumeWell.py
+++ /dev/null
@@ -1,346 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0
-# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Martin Lemay
-# ruff: noqa: E402 # disable Module level import not at top of file
-import sys
-from pathlib import Path
-
-import numpy as np
-import numpy.typing as npt
-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 typing_extensions import Self
-from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector
-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
-
-update_paths()
-
-from geos.utils.GeosOutputsConstants import (
- GeosMeshOutputsEnum,
- getAttributeToTransferFromInitialTime,
-)
-from geos.utils.Logger import ERROR, INFO, Logger, getLogger
-from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
-from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
-from geos.mesh.utils.arrayModifiers import (
- copyAttribute,
- createCellCenterAttribute,
-)
-from geos.pv.utils.paraviewTreatments import getTimeStepIndex
-
-__doc__ = """
-PVExtractMergeBlocksVolumeWell is a Paraview plugin that allows to merge
-ranks of a Geos output objects containing a volumic mesh and wells.
-
-Input and output types are vtkMultiBlockDataSet.
-
-This filter results in 2 output pipelines:
-
-* first pipeline contains the volume mesh. If multiple regions were defined in
- the volume mesh, they are preserved as distinct blocks.
-* second pipeline contains wells. If multiple wells were used, they are preserved
- as distinct blocks.
-
-To use it:
-
-* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVExtractMergeBlocksVolumeWell.
-* Select the Geos output .pvd file loaded in Paraview.
-* Search and Apply PVExtractMergeBlocksVolumeWell Filter.
-
-"""
-
-
-@smproxy.filter(
- name="PVExtractMergeBlocksVolumeWell",
- label="Geos Extract And Merge Blocks - Volume/Well",
-)
-@smhint.xml( """
-
-
-
- """ )
-@smproperty.input( name="Input", port_index=0 )
-@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
-class PVExtractMergeBlocksVolumeWell( VTKPythonAlgorithmBase ):
-
- def __init__( self: Self ) -> None:
- """Paraview plugin to extract and merge ranks from Geos output Mesh.
-
- To apply in the case of output ".pvd" file contains Volume and Well
- elements.
- """
- super().__init__(
- nInputPorts=1,
- nOutputPorts=2,
- inputType="vtkMultiBlockDataSet",
- outputType="vtkMultiBlockDataSet",
- )
-
- #: all time steps from input
- self.m_timeSteps: npt.NDArray[ np.float64 ] = np.array( [] )
- #: displayed time step in the IHM
- self.m_currentTime: float = 0.0
- #: time step index of displayed time step
- self.m_currentTimeStepIndex: int = 0
- #: request data processing step - incremented each time RequestUpdateExtent is called
- self.m_requestDataStep: int = -1
-
- # saved object at initial time step
- self.m_outputT0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
-
- # set logger
- self.m_logger: Logger = getLogger( "Extract and merge block Filter" )
-
- def SetLogger( self: Self, logger: Logger ) -> None:
- """Set filter logger.
-
- Args:
- logger (Logger): logger
- """
- self.m_logger = logger
-
- 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(), "vtkMultiBlockDataSet" )
- return 1
-
- 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,
- 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
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- inData = self.GetInputData( inInfoVec, 0, 0 )
- outDataCells = self.GetOutputData( outInfoVec, 0 )
- outDataWells = self.GetOutputData( outInfoVec, 1 )
- assert inData is not None
- if outDataCells is None or ( not outDataCells.IsA( "vtkMultiBlockDataSet" ) ):
- outDataCells = vtkMultiBlockDataSet()
- outInfoVec.GetInformationObject( 0 ).Set(
- outDataCells.DATA_OBJECT(),
- outDataCells # type: ignore
- )
-
- if outDataWells is None or ( not outDataWells.IsA( "vtkMultiBlockDataSet" ) ):
- outDataWells = vtkMultiBlockDataSet()
- outInfoVec.GetInformationObject( 1 ).Set(
- outDataWells.DATA_OBJECT(),
- outDataWells # type: ignore
- )
-
- return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return]
-
- def RequestUpdateExtent(
- self: Self,
- request: vtkInformation, # noqa: F841
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestUpdateExtent.
-
- 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()
- inInfo = inInfoVec[ 0 ]
- # get displayed time step info before updating time
- if self.m_requestDataStep == -1:
- self.m_logger.info( f"Apply filter {__name__}" )
- self.m_timeSteps = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() # type: ignore
- )
- self.m_currentTime = inInfo.GetInformationObject( 0 ).Get( executive.UPDATE_TIME_STEP() # type: ignore
- )
- self.m_currentTimeStepIndex = getTimeStepIndex( self.m_currentTime, self.m_timeSteps )
- # update requestDataStep
- self.m_requestDataStep += 1
-
- # update time according to requestDataStep iterator
- inInfo.GetInformationObject( 0 ).Set(
- executive.UPDATE_TIME_STEP(),
- self.m_timeSteps[ self.m_requestDataStep ] # type: ignore
- )
- outInfoVec.GetInformationObject( 0 ).Set(
- executive.UPDATE_TIME_STEP(),
- self.m_timeSteps[ self.m_requestDataStep ] # type: ignore
- )
-
- # update all objects according to new time info
- self.Modified()
- return 1
-
- def RequestData(
- self: Self,
- request: vtkInformation,
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestData.
-
- Args:
- request (vtkInformation): request
- inInfoVec (list[vtkInformationVector]): input objects
- outInfoVec (vtkInformationVector): output objects
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- try:
- input: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
- outputCells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 )
- outputWells: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 1 )
-
- assert input is not None, "Input MultiBlockDataSet is null."
- assert outputCells is not None, "Output volum mesh is null."
- assert outputWells is not None, "Output well mesh is null."
-
- # time controller
- executive = self.GetExecutive()
- if self.m_requestDataStep == 0:
- # first time step
- # do extraction and merge (do not display phase info)
- self.m_logger.setLevel( ERROR )
- outputWells0: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
- self.doExtractAndMerge( input, outputCells, outputWells0 )
- self.m_logger.setLevel( INFO )
- # save input mesh to copy later
- self.m_outputT0.ShallowCopy( outputCells )
- request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore
- if self.m_requestDataStep >= self.m_currentTimeStepIndex:
- # displayed time step, no need to go further
- request.Remove( executive.CONTINUE_EXECUTING() ) # type: ignore
- # reinitialize requestDataStep if filter is recalled later
- self.m_requestDataStep = -1
- # do extraction and merge
- self.doExtractAndMerge( input, outputCells, outputWells )
- # copy attributes from initial time step
- for (
- attributeName,
- attributeNewName,
- ) in getAttributeToTransferFromInitialTime().items():
- copyAttribute( self.m_outputT0, outputCells, attributeName, attributeNewName )
- # create elementCenter attribute if needed
- cellCenterAttributeName: str = ( GeosMeshOutputsEnum.ELEMENT_CENTER.attributeName )
- createCellCenterAttribute( outputCells, cellCenterAttributeName )
-
- except AssertionError as e:
- mess: str = "Block extraction and merge failed due to:"
- self.m_logger.error( mess )
- self.m_logger.error( str( e ) )
- return 0
- except Exception as e:
- mess1: str = "Block extraction and merge failed due to:"
- self.m_logger.critical( mess1 )
- self.m_logger.critical( e, exc_info=True )
- return 0
-
- return 1
-
- def doExtractAndMerge(
- self: Self,
- input: vtkMultiBlockDataSet,
- outputCells: vtkMultiBlockDataSet,
- outputWells: vtkMultiBlockDataSet,
- ) -> bool:
- """Apply block extraction and merge.
-
- Args:
- input (vtkMultiBlockDataSet): input multi block
- outputCells (vtkMultiBlockDataSet): output volume mesh
- outputWells (vtkMultiBlockDataSet): output well mesh
-
- Returns:
- bool: True if extraction and merge successfully eneded, False otherwise
- """
- # extract blocks
- blockExtractor: GeosBlockExtractor = GeosBlockExtractor( input, extractWells=True )
- blockExtractor.applyFilter()
-
- # recover output objects from GeosBlockExtractor filter and merge internal blocks
- volumeBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.volume
- assert volumeBlockExtracted is not None, "Extracted Volume mesh is null."
- outputCells.ShallowCopy( self.mergeBlocksFilter( volumeBlockExtracted, False ) )
-
- wellBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.well
- assert wellBlockExtracted is not None, "Extracted Well mesh is null."
- outputWells.ShallowCopy( self.mergeBlocksFilter( wellBlockExtracted, False ) )
-
- outputCells.Modified()
- outputWells.Modified()
-
- self.m_logger.info( "Volume blocks were successfully splitted from " +
- "wells, and ranks were merged together." )
- return True
-
- def mergeBlocksFilter( self: Self,
- input: vtkMultiBlockDataSet,
- convertSurfaces: bool = False ) -> vtkMultiBlockDataSet:
- """Apply vtk merge block filter on input multi block mesh.
-
- Args:
- input (vtkMultiBlockDataSet): multiblock mesh to merge
- convertSurfaces (bool, optional): True to convert surface from vtkUnstructuredGrid to
- vtkPolyData. Defaults to False.
-
- Returns:
- vtkMultiBlockDataSet: Multiblock mesh composed of internal merged blocks.
- """
- mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( input, convertSurfaces, True )
- if not mergeBlockFilter.logger.hasHandlers():
- mergeBlockFilter.setLoggerHandler( VTKHandler() )
- mergeBlockFilter.applyFilter()
- mergedBlocks: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
- assert mergedBlocks is not None, "Final merged MultiBlockDataSet is null."
- return mergedBlocks
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolume.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolume.py
index 50a6a0f35..7c550c37a 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolume.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolume.py
@@ -30,7 +30,7 @@
VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
)
-from geos.pv.plugins.PVExtractMergeBlocksVolume import PVExtractMergeBlocksVolume
+from geos.pv.plugins.PVGeosBlockExtractAndMerge import PVGeosBlockExtractAndMerge
from geos.pv.plugins.PVGeomechanicsCalculator import PVGeomechanicsCalculator
__doc__ = """
@@ -353,12 +353,12 @@ def doExtractAndMerge( self: Self ) -> bool:
Returns:
bool: True if extraction and merge successfully eneded, False otherwise
"""
- filter: PVExtractMergeBlocksVolume = PVExtractMergeBlocksVolume()
+ filter: PVGeosBlockExtractAndMerge = PVGeosBlockExtractAndMerge()
filter.SetInputConnection( self.GetInputConnection( 0, 0 ) )
filter.SetLogger( self.m_logger )
filter.Update()
- # recover output objects from PVExtractMergeBlocksVolume
+ # recover output objects from PVGeosBlockExtractAndMerge
self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
self.m_volumeMesh.Modified()
return True
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurface.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurface.py
index c1370de65..d383c457a 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurface.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurface.py
@@ -30,8 +30,7 @@
DEFAULT_ROCK_COHESION,
WATER_DENSITY,
)
-from geos.pv.plugins.PVExtractMergeBlocksVolumeSurface import (
- PVExtractMergeBlocksVolumeSurface, )
+from geos.pv.plugins.PVGeosBlockExtractAndMerge import PVGeosBlockExtractAndMerge
from geos.pv.plugins.PVGeomechanicsCalculator import PVGeomechanicsCalculator
from geos.pv.plugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics
@@ -352,12 +351,12 @@ def doExtractAndMerge( self: Self ) -> bool:
Returns:
bool: True if extraction and merge successfully eneded, False otherwise
"""
- filter: PVExtractMergeBlocksVolumeSurface = PVExtractMergeBlocksVolumeSurface()
+ filter: PVGeosBlockExtractAndMerge = PVGeosBlockExtractAndMerge()
filter.SetInputConnection( self.GetInputConnection( 0, 0 ) )
filter.SetLogger( self.m_logger )
filter.Update()
- # recover output objects from PVExtractMergeBlocksVolumeSurface
+ # recover output objects from PVGeosBlockExtractAndMerge
self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
self.m_surfaceMesh.ShallowCopy( filter.GetOutputDataObject( 1 ) )
self.m_volumeMesh.Modified()
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py
index 693c6055b..3c8ac8fbf 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py
@@ -30,8 +30,7 @@
DEFAULT_ROCK_COHESION,
WATER_DENSITY,
)
-from geos.pv.plugins.PVExtractMergeBlocksVolumeSurfaceWell import (
- PVExtractMergeBlocksVolumeSurfaceWell, )
+from geos.pv.plugins.PVGeosBlockExtractAndMerge import PVGeosBlockExtractAndMerge
from geos.pv.plugins.PVGeomechanicsCalculator import PVGeomechanicsCalculator
from geos.pv.plugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics
@@ -359,12 +358,12 @@ def doExtractAndMerge( self: Self ) -> bool:
Returns:
bool: True if extraction and merge successfully eneded, False otherwise
"""
- filter: PVExtractMergeBlocksVolumeSurfaceWell = ( PVExtractMergeBlocksVolumeSurfaceWell() )
+ filter: PVGeosBlockExtractAndMerge = PVGeosBlockExtractAndMerge()
filter.SetInputConnection( self.GetInputConnection( 0, 0 ) )
filter.SetLogger( self.m_logger )
filter.Update()
- # recover output objects from PVExtractMergeBlocksVolumeSurfaceWell
+ # recover output objects from PVGeosBlockExtractAndMerge
self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
self.m_surfaceMesh.ShallowCopy( filter.GetOutputDataObject( 1 ) )
self.m_wells.ShallowCopy( filter.GetOutputDataObject( 2 ) )
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeWell.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeWell.py
index 1e7c99807..44aeb977d 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeWell.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeWell.py
@@ -31,8 +31,7 @@
WATER_DENSITY,
)
-from geos.pv.plugins.PVExtractMergeBlocksVolumeWell import (
- PVExtractMergeBlocksVolumeWell, )
+from geos.pv.plugins.PVGeosBlockExtractAndMerge import PVGeosBlockExtractAndMerge
from geos.pv.plugins.PVGeomechanicsCalculator import PVGeomechanicsCalculator
__doc__ = """
@@ -366,12 +365,12 @@ def doExtractAndMerge( self: Self ) -> bool:
Returns:
bool: True if extraction and merge successfully eneded, False otherwise
"""
- filter: PVExtractMergeBlocksVolumeWell = PVExtractMergeBlocksVolumeWell()
+ filter: PVGeosBlockExtractAndMerge = PVGeosBlockExtractAndMerge()
filter.SetInputConnection( self.GetInputConnection( 0, 0 ) )
filter.SetLogger( self.m_logger )
filter.Update()
- # recover output objects from PVExtractMergeBlocksVolumeWell
+ # recover output objects from PVGeosBlockExtractAndMerge
self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
self.m_wells.ShallowCopy( filter.GetOutputDataObject( 1 ) )
self.m_volumeMesh.Modified()
From 0569296e2b7fb498e577fc2a3de4150aa2d9aa5b Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Wed, 12 Nov 2025 17:33:12 +0100
Subject: [PATCH 49/62] Update GEOS output attribute name
---
.../src/geos/utils/GeosOutputsConstants.py | 24 +++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/geos-utils/src/geos/utils/GeosOutputsConstants.py b/geos-utils/src/geos/utils/GeosOutputsConstants.py
index ac4190002..b066d60ad 100644
--- a/geos-utils/src/geos/utils/GeosOutputsConstants.py
+++ b/geos-utils/src/geos/utils/GeosOutputsConstants.py
@@ -237,20 +237,36 @@ def __init__( self: Self, phaseType: str, attributes: tuple[ str, ...] ) -> None
(
GeosMeshSuffixEnum.DENSITY_SUFFIX.value,
GeosMeshSuffixEnum.STRESS_SUFFIX.value,
- GeosMeshSuffixEnum.BULK_MODULUS_SUFFIX.value,
- GeosMeshSuffixEnum.SHEAR_MODULUS_SUFFIX.value,
+ GeosMeshSuffixEnum.PERMEABILITY_SUFFIX.value,
GeosMeshSuffixEnum.POROSITY_SUFFIX.value,
+ GeosMeshSuffixEnum.POROSITY_INIT_SUFFIX.value,
GeosMeshSuffixEnum.POROSITY_REF_SUFFIX.value,
- GeosMeshSuffixEnum.PERMEABILITY_SUFFIX.value,
+ GeosMeshSuffixEnum.BULK_MODULUS_SUFFIX.value,
+ GeosMeshSuffixEnum.SHEAR_MODULUS_SUFFIX.value,
+ GeosMeshSuffixEnum.GRAIN_BULK_MODULUS_SUFFIX.value,
+ GeosMeshSuffixEnum.BIOT_COEFFICIENT_SUFFIX.value,
),
)
FLUID = (
"Fluid",
(
+ GeosMeshSuffixEnum.TOTAL_FLUID_DENSITY_SUFFIX.value,
GeosMeshSuffixEnum.PHASE_DENSITY_SUFFIX.value,
- GeosMeshSuffixEnum.PHASE_VISCOSITY_SUFFIX.value,
GeosMeshSuffixEnum.PHASE_FRACTION_SUFFIX.value,
GeosMeshSuffixEnum.PHASE_MASS_DENSITY_SUFFIX.value,
+ GeosMeshSuffixEnum.PHASE_VISCOSITY_SUFFIX.value,
+ GeosMeshSuffixEnum.PHASE_INTERNAL_ENERGY_SUFFIX.value,
+ GeosMeshSuffixEnum.PHASE_COMP_FRACTION_SUFFIX.value,
+ GeosMeshSuffixEnum.PHASE_REAL_PERM_SUFFIX.value,
+ GeosMeshSuffixEnum.PHASE_TRAP_VOL_FRACTION_SUFFIX.value,
+ GeosMeshSuffixEnum.FLUID_DENSITY_SUFFIX.value,
+ GeosMeshSuffixEnum.FLUID_VISCOSITY_SUFFIX.value,
+ GeosMeshSuffixEnum.FLUID_DENSITY_DERIVATE_SUFFIX.value,
+ GeosMeshSuffixEnum.FLUID_VISCOSITY_DERIVATE_SUFFIX.value,
+ GeosMeshSuffixEnum.FLUID_INTERNAL_ENERGY_SUFFIX.value,
+ GeosMeshSuffixEnum.FLUID_INTERNAL_ENERGY_DERIVATE_SUFFIX.value,
+ GeosMeshSuffixEnum.FLUID_ENTHALPY_SUFFIX.value,
+ GeosMeshSuffixEnum.FLUID_ENTHALPY_DERIVATE_SUFFIX.value,
),
)
UNKNOWN = ( "Other", () )
From 065f12645ab327b9210ab9f664b8ca36f8676782 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Wed, 12 Nov 2025 18:08:34 +0100
Subject: [PATCH 50/62] fix the doc
---
.../pv/plugins/PVGeosBlockExtractAndMerge.py | 34 +++++++++----------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py b/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
index 01dd30c7f..abd6e4990 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
@@ -36,29 +36,29 @@
VTKHandler )
__doc__ = """
-PVGeosBlockExtractAndMerge is a Paraview plugin that:
- Allows to extract domains (volume, fault and well) from a GEOS output multiBlockDataSet mesh
- Acts on each region of a GEOS output domain (volume, fault, wells) to:
- Merge Ranks
- Identify "Fluids" and "Rock" phases
- Rename "Rock" attributes depending on the phase they refer to for more clarity
- Convert volume meshes to surface if needed
- Copy "geomechanics" attributes from the initial timestep to the current one if their exist
+PVGeosBlockExtractAndMerge is a Paraview plugin processing the input mesh at the current time in two steps:
+ - First extracts domains (volume, fault and well) from a GEOS output multiBlockDataSet mesh
+ - Second Acts on each region of a GEOS output domain (volume, fault, wells) to:
+ * Merge Ranks
+ * Identify "Fluids" and "Rock" phases
+ * Rename "Rock" attributes depending on the phase they refer to for more clarity
+ * Convert volume meshes to surface if needed
+ * Copy "geomechanics" attributes from the initial timestep to the current one if their exist
This filter results in 3 output pipelines with the vtkMultiBlockDataSet:
- "Volume" contains the volume domain
- "Fault" contains the fault domain if it exist
- "Well" contains the well domain if it exist
+ - "Volume" contains the volume domain
+ - "Fault" contains the fault domain if it exist
+ - "Well" contains the well domain if it exist
Input and output meshes are vtkMultiBlockDataSet.
.. Important::
- The input mesh must be an output of a GEOS simulation or contain at least three blocks labeled with the same domain names:
- "CellElementRegion" for volume domain
- "SurfaceElementRegion" for fault domain if the input contains fault
- "WellElementRegion" for well domain if the input contains well
- See more https://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/datastructure/ElementRegions.html?_sm_au_=iVVT5rrr5fN00R8sQ0WpHK6H8sjL6#xml-element-elementregions
- The filter detected automatically the three domains. If one of them if not in the input mesh, the associated output pipeline will be empty
+ - The input mesh must be an output of a GEOS simulation or contain at least three blocks labeled with the same domain names:
+ * "CellElementRegion" for volume domain
+ * "SurfaceElementRegion" for fault domain if the input contains fault
+ * "WellElementRegion" for well domain if the input contains well
+ * See more https://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/datastructure/ElementRegions.html?_sm_au_=iVVT5rrr5fN00R8sQ0WpHK6H8sjL6#xml-element-elementregions
+ - The filter detected automatically the three domains. If one of them if not in the input mesh, the associated output pipeline will be empty
To use it:
From b6a716112d0e2028dc20282a3cac297da5e6aafe Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Wed, 12 Nov 2025 18:18:54 +0100
Subject: [PATCH 51/62] fix import
---
.../src/geos/processing/post_processing/GeosBlockMerge.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index ef4e54c78..5bee01e71 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -17,7 +17,7 @@
from geos.mesh.utils.arrayModifiers import ( createConstantAttribute, renameAttribute )
from geos.mesh.utils.multiblockModifiers import mergeBlocks
from geos.mesh.utils.multiblockHelpers import ( getElementaryCompositeBlockIndexes, extractBlock )
-from geos.mesh.utils.genericHelpers import ( computeNormals, computeTangents, convertTriangulateBlockToSurface,
+from geos.mesh.utils.genericHelpers import ( computeNormals, computeTangents, convertUnstructuredGridToPolyData,
triangulateMesh, isTriangulate )
__doc__ = """
@@ -162,7 +162,7 @@ def applyFilter( self: Self ) -> None:
if self.convertFaultToSurface:
if not isTriangulate( volumeMesh ):
volumeMesh.ShallowCopy( triangulateMesh( volumeMesh ) )
- surfaceMesh: vtkPolyData = convertTriangulateBlockToSurface( volumeMesh )
+ surfaceMesh: vtkPolyData = convertUnstructuredGridToPolyData( volumeMesh )
surfaceMesh.ShallowCopy( computeNormals( surfaceMesh, logger=self.logger ) )
surfaceMesh.ShallowCopy( computeTangents( surfaceMesh, logger=self.logger ) )
# Add the merged block to the output mesh
From 0f5882ae5ae3cbcfa978cb367202fd95ec9eb118 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 13 Nov 2025 07:59:07 +0100
Subject: [PATCH 52/62] fix yapf
---
geos-utils/src/geos/utils/GeosOutputsConstants.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/geos-utils/src/geos/utils/GeosOutputsConstants.py b/geos-utils/src/geos/utils/GeosOutputsConstants.py
index b066d60ad..0dc0e6917 100644
--- a/geos-utils/src/geos/utils/GeosOutputsConstants.py
+++ b/geos-utils/src/geos/utils/GeosOutputsConstants.py
@@ -12,7 +12,6 @@
.. WARNING::
Names may need to be updated when modifications occur in the GEOS code.
-
.. todo::
If possible, link GEOS names directly with GEOS code instead of redefining
@@ -123,7 +122,6 @@ class GeosMeshSuffixEnum( Enum ):
FLUID_ENTHALPY_SUFFIX = "_enthalpy"
FLUID_ENTHALPY_DERIVATE_SUFFIX = "_dEnthalpy"
-
# surface attribute transfer suffix
SURFACE_PLUS_SUFFIX = "_Plus"
SURFACE_MINUS_SUFFIX = "_Minus"
From f3ab71ec337119d4d4b6265da3659105b10e8243 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 13 Nov 2025 08:05:44 +0100
Subject: [PATCH 53/62] Rename the plugin name
---
...nicsWorkflowVolumeSurfaceWell.py => PVGeomechanicsWorkflow.py} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename geos-pv/src/geos/pv/plugins/{PVGeomechanicsWorkflowVolumeSurfaceWell.py => PVGeomechanicsWorkflow.py} (100%)
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
similarity index 100%
rename from geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurfaceWell.py
rename to geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
From bae6f34092183d4c8855616305188cfb40df5628 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 13 Nov 2025 08:56:15 +0100
Subject: [PATCH 54/62] Update the doc
---
docs/geos_pv_docs/processing.rst | 6 +-
.../geos/pv/plugins/PVGeomechanicsWorkflow.py | 78 +++++++++++++------
2 files changed, 58 insertions(+), 26 deletions(-)
diff --git a/docs/geos_pv_docs/processing.rst b/docs/geos_pv_docs/processing.rst
index 368009192..ff91f42dc 100644
--- a/docs/geos_pv_docs/processing.rst
+++ b/docs/geos_pv_docs/processing.rst
@@ -70,10 +70,10 @@ PVGeomechanicsWorkflowVolumeSurface plugin
.. automodule:: geos.pv.plugins.PVGeomechanicsWorkflowVolumeSurface
-PVGeomechanicsWorkflowVolumeSurfaceWell plugin
---------------------------------------------------------
+PVGeomechanicsWorkflow plugin
+------------------------------
-.. automodule:: geos.pv.plugins.PVGeomechanicsWorkflowVolumeSurfaceWell
+.. automodule:: geos.pv.plugins.PVGeomechanicsWorkflow
PVGeomechanicsWorkflowVolumeWell plugin
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
index 3c8ac8fbf..37517a692 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
@@ -35,39 +35,71 @@
from geos.pv.plugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics
__doc__ = """
-PVGeomechanicsWorkflowVolumeSurfaceWell is a Paraview plugin that execute
-multiple filters to clean GEOS outputs and compute additional geomechanical
-outputs on volume, surface and wells.
-
-Input and output types are vtkMultiBlockDataSet.
-
-This filter results in 3 output pipelines:
-
-* first pipeline contains the volume mesh. If multiple regions were defined in
- the volume mesh, they are preserved as distinct blocks.
-* second pipeline contains surfaces. If multiple surfaces were used, they are
- preserved as distinct blocks.
-* third pipeline contains wells. If multiple wells were used, they are preserved
- as distinct blocks.
+PVGeomechanicsWorkflow is a Paraview plugin that executes multiple plugins:
+ - First PVGeosBlockExtractAndMerge
+ - Secondly PVGeomechanicsCalculator
+ - Thirdly PVSurfaceGeomechanics (if the input mesh contains faults)
+
+PVGeosBlockExtractAndMerge is a Paraview plugin processing the input mesh at the current time in two steps:
+ - First extracts domains (volume, fault and well) from a GEOS output multiBlockDataSet mesh
+ - Second Acts on each region of a GEOS output domain (volume, fault, wells) to:
+ * Merge Ranks
+ * Identify "Fluids" and "Rock" phases
+ * Rename "Rock" attributes depending on the phase they refer to for more clarity
+ * Convert volume meshes to surface if needed
+ * Copy "geomechanics" attributes from the initial timestep to the current one if their exist
+
+PVGeomechanicsCalculator is a paraview plugin that allows to compute basic and advanced geomechanics properties from existing ones in the mesh. This is donne on each block of the volume mesh.
+
+The basic geomechanics properties computed on the mesh are:
+ - The elastic moduli not present on the mesh
+ - 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
+ - Elastic stain
+ - Real reservoir stress path and reservoir stress path in oedometric condition
+
+The advanced geomechanics properties computed on the mesh are:
+ - Fracture index and threshold
+ - Critical pore pressure and pressure index
+
+PVSurfaceGeomechanics is a Paraview plugin that allows to compute additional geomechanical attributes from the input surfaces, such as shear capacity utilization (SCU). This is donne on each block of the fault mesh.
+
+This filter results in 3 output pipelines with the vtkMultiBlockDataSet:
+ - "Volume" contains the volume domain
+ - "Fault" contains the fault domain if it exist
+ - "Well" contains the well domain if it exist
+
+Input and output meshes are vtkMultiBlockDataSet.
To use it:
-* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVGeomechanicsWorkflowVolumeSurfaceWell.
+* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVGeomechanicsWorkflow.
* Select the Geos output .pvd file loaded in Paraview.
-* Search and Apply PVGeomechanicsWorkflowVolumeSurfaceWell Filter.
+* Select Filters > 3- Geos Geomechanics > Geos Geomechanics Workflow.
+* Change the physical constants if needed
+* Select computeAdvancedProperties to compute the advanced properties on volume mesh
+* Apply PVGeomechanicsWorkflow
"""
@smproxy.filter(
- name="PVGeomechanicsWorkflowVolumeSurfaceWell",
- label="Geos Geomechanics Workflow - Volume/Surface/Well",
+ name="PVGeomechanicsWorkflow",
+ label="Geos Geomechanics Workflow",
)
-@smhint.xml( """
-
-
-
-
+@smproperty.xml( """
+
+
+
+
+
+
+
+
+
""" )
@smproperty.input( name="Input", port_index=0 )
@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
From 596a2af70410ba2df0f334bc4a4674d0635d63fe Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 13 Nov 2025 10:50:20 +0100
Subject: [PATCH 55/62] Refactor the plugin
---
.../geos/pv/plugins/PVGeomechanicsWorkflow.py | 356 ++++++++----------
1 file changed, 158 insertions(+), 198 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
index 37517a692..542521ff7 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
@@ -1,15 +1,12 @@
# 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 sys
-from pathlib import Path
+import logging
-import numpy as np
+from pathlib import Path
from typing_extensions import Self
-from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector
-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
@@ -18,22 +15,25 @@
update_paths()
-from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
- VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
-)
-
-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.pv.plugins.PVGeosBlockExtractAndMerge import PVGeosBlockExtractAndMerge
from geos.pv.plugins.PVGeomechanicsCalculator import PVGeomechanicsCalculator
from geos.pv.plugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics
+from vtkmodules.vtkCommonCore import ( vtkInformation, vtkInformationVector )
+from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet
+
+from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
+ VTKPythonAlgorithmBase, smdomain, smproperty, smproxy )
+from paraview.detail.loghandler import ( # type: ignore[import-not-found]
+ VTKHandler )
+
__doc__ = """
PVGeomechanicsWorkflow is a Paraview plugin that executes multiple plugins:
- First PVGeosBlockExtractAndMerge
@@ -85,6 +85,8 @@
"""
+loggerTitle: str = "GEOS Geomechanics Workflow"
+
@smproxy.filter(
name="PVGeomechanicsWorkflow",
@@ -103,12 +105,12 @@
""" )
@smproperty.input( name="Input", port_index=0 )
@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
-class PVGeomechanicsWorkflowVolumeSurfaceWell( VTKPythonAlgorithmBase ):
+class PVGeomechanicsWorkflow( VTKPythonAlgorithmBase ):
def __init__( self: Self ) -> None:
"""Paraview plugin to clean and add new outputs Geos output mesh.
- To apply in the case of output ".pvd" file contains Volume, Fault and
+ To apply in the case of output ".pvd" file contains Volume, Fault or
Well elements.
"""
super().__init__(
@@ -118,21 +120,26 @@ def __init__( self: Self ) -> None:
outputType="vtkMultiBlockDataSet",
)
- #: ouput volume mesh
- self.m_volumeMesh: vtkMultiBlockDataSet
- #: output surface mesh
- self.m_surfaceMesh: vtkMultiBlockDataSet
- #: output wells
- self.m_wells: vtkMultiBlockDataSet
+ self.volumeMesh: vtkMultiBlockDataSet
+ self.faultMesh: vtkMultiBlockDataSet
+ self.wellMesh: vtkMultiBlockDataSet
- 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_RAD
+ self.extractFault: bool = True
+ self.extractWell: bool = True
- # set logger
- self.m_logger: Logger = getLogger( "Geomechanics Workflow Filter" )
+ self.computeAdvancedProperties: bool = False
+
+ # Defaults physical constants
+ ## For basic properties on Volume
+ self.grainBulkModulus: float = DEFAULT_GRAIN_BULK_MODULUS
+ self.specificDensity: float = WATER_DENSITY
+ ## For advanced properties on Volume and basic properties on Surface
+ self.rockCohesion: float = DEFAULT_ROCK_COHESION
+ self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_DEG
+
+ self.logger = logging.getLogger( loggerTitle )
+ self.logger.setLevel( logging.INFO )
+ self.logger.addHandler( VTKHandler() )
@smproperty.doublevector(
name="GrainBulkModulus",
@@ -146,23 +153,15 @@ def __init__( self: Self ) -> 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",
label="Specific Density (kg/m3)",
@@ -175,30 +174,22 @@ 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( """
-
+
""" )
- def b09GroupBasicOutputParameters( self: Self ) -> None:
+ def groupBasicPropertiesParameters( self: Self ) -> None:
"""Organize groups."""
self.Modified()
@@ -209,28 +200,20 @@ def b09GroupBasicOutputParameters( 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 (°)",
@@ -238,28 +221,20 @@ def getRockCohesion( self: Self ) -> float:
panel_visibility="default",
)
@smdomain.xml( """
-
- Reference friction angle to compute critical pore pressure.
- The unit is °. Default is 10°.
-
- """ )
- def d02SetFrictionAngle( self: Self, value: float ) -> None:
- """Set frition angle.
+
+ Reference friction angle to compute critical pore pressure.
+ The unit is °. Default is no friction case (i.e., 0.°).
+
+ """ )
+ def setFrictionAngle( self: Self, frictionAngle: float ) -> None:
+ """Set friction angle.
Args:
- value (float): friction angle (°)
+ frictionAngle (float): Friction angle (°).
"""
- self.m_frictionAngle = value * np.pi / 180.0
+ self.frictionAngle = frictionAngle
self.Modified()
- def getFrictionAngle( self: Self ) -> float:
- """Get friction angle in radian.
-
- Returns:
- float: friction angle.
- """
- return self.m_frictionAngle
-
@smproperty.xml( """
@@ -267,61 +242,39 @@ def getFrictionAngle( self: Self ) -> float:
""" )
- def d09GroupAdvancedOutputParameters( self: Self ) -> None:
+ def groupAdvancedPropertiesAndSurfaceParameters( self: Self ) -> None:
"""Organize groups."""
self.Modified()
@smproperty.intvector(
- name="AdvancedOutputsUse",
- label="Compute advanced geomechanical volume outputs",
+ name="ComputeAdvancedProperties",
+ label="Compute advanced geomechanics properties",
default_values=0,
panel_visibility="default",
)
@smdomain.xml( """
-
-
- Check to compute advanced geomechanical outputs including
- reservoir stress paths and fracture indexes.
-
- """ )
- def e01SetAdvancedOutputs( self: Self, boolean: bool ) -> None:
- """Set advanced output calculation option.
+
+
+ Check to compute advanced geomechanics properties including
+ reservoir stress paths and fracture indexes.
+
+ """ )
+ def setComputeAdvancedProperties( self: Self, computeAdvancedProperties: bool ) -> None:
+ """Set advanced properties calculation option.
Args:
- boolean (bool): if True advanced outputs are computed.
+ computeAdvancedProperties (bool): True to compute advanced geomechanics properties, False otherwise.
"""
- self.m_computeAdvancedOutputs = boolean
+ self.computeAdvancedProperties = computeAdvancedProperties
self.Modified()
- def getComputeAdvancedOutputs( self: Self ) -> float:
- """Access the advanced outputs option.
-
- Returns:
- float: self.m_computeAdvancedOutputs.
- """
- return self.m_computeAdvancedOutputs
-
- 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(), "vtkMultiBlockDataSet" )
- return 1
-
- def RequestInformation(
+ def RequestDataObject(
self: Self,
- request: vtkInformation, # noqa: F841
- inInfoVec: list[ vtkInformationVector ], # noqa: F841
+ request: vtkInformation,
+ inInfoVec: list[ vtkInformationVector ],
outInfoVec: vtkInformationVector,
) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestInformation.
+ """Inherited from VTKPythonAlgorithmBase::RequestDataObject.
Args:
request (vtkInformation): request
@@ -331,9 +284,25 @@ def RequestInformation(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
- executive = self.GetExecutive() # noqa: F841
- outInfo: vtkInformation = outInfoVec.GetInformationObject( 0 ) # noqa: F841
- return 1
+ inData = self.GetInputData( inInfoVec, 0, 0 )
+ assert inData is not None
+
+ outDataCells = self.GetOutputData( outInfoVec, 0 )
+ if outDataCells is None or ( not outDataCells.IsA( "vtkMultiBlockDataSet" ) ):
+ outDataCells = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 0 ).Set( outDataCells.DATA_OBJECT(), outDataCells ) # type: ignore
+
+ outDataFaults = self.GetOutputData( outInfoVec, 1 )
+ if outDataFaults is None or ( not outDataFaults.IsA( "vtkMultiBlockDataSet" ) ):
+ outDataFaults = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 1 ).Set( outDataFaults.DATA_OBJECT(), outDataFaults ) # type: ignore
+
+ outDataWells = self.GetOutputData( outInfoVec, 2 )
+ if outDataWells is None or ( not outDataWells.IsA( "vtkMultiBlockDataSet" ) ):
+ outDataWells = vtkMultiBlockDataSet()
+ outInfoVec.GetInformationObject( 2 ).Set( outDataWells.DATA_OBJECT(), outDataWells ) # type: ignore
+
+ return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return]
def RequestData(
self: Self,
@@ -351,89 +320,80 @@ def RequestData(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
+ self.logger.info( f"Apply plugin { self.logger.name }." )
try:
- input: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
- self.m_volumeMesh = self.GetOutputData( outInfoVec, 0 )
- self.m_surfaceMesh = self.GetOutputData( outInfoVec, 1 )
- self.m_wells = self.GetOutputData( outInfoVec, 2 )
-
- assert input is not None, "Input MultiBlockDataSet is null."
- assert self.m_volumeMesh is not None, "Output volume mesh is null."
- assert self.m_surfaceMesh is not None, "Output surface mesh is null."
- assert self.m_wells is not None, "Output well mesh is null."
-
- # 1. extract volume/surface/wells
- self.doExtractAndMerge()
- # 2. compute Geomechanical outputs in volume mesh
- self.computeAdditionalOutputsVolume()
- # 3. compute geomechanical outputs on surface mesh
- self.computeSurfaceGeomecanics()
+ self.volumeMesh = self.GetOutputData( outInfoVec, 0 )
+ self.faultMesh = self.GetOutputData( outInfoVec, 1 )
+ self.wellMesh = self.GetOutputData( outInfoVec, 2 )
+
+ self.applyPVGeosBlockExtractAndMerge()
+ self.applyPVGeomechanicsCalculator()
+
+ if self.extractFault:
+ self.applyPVSurfaceGeomechanics()
+
+ self.logger.info( f"The plugin { self.logger.name } succeeded." )
except AssertionError as e:
mess: str = "Geomechanics workflow failed due to:"
- self.m_logger.error( mess )
- self.m_logger.error( str( e ) )
+ self.logger.error( mess )
+ self.logger.error( str( e ) )
return 0
except Exception as e:
mess1: str = "Geomechanics workflow failed due to:"
- self.m_logger.critical( mess1 )
- self.m_logger.critical( e, exc_info=True )
+ self.logger.critical( mess1 )
+ self.logger.critical( e, exc_info=True )
return 0
return 1
- def doExtractAndMerge( self: Self ) -> bool:
- """Apply block extraction and merge filter.
+ def applyPVGeosBlockExtractAndMerge( self: Self ) -> None:
+ """Apply PVGeosBlockExtractAndMerge."""
+ extractAndMergeFilter: PVGeosBlockExtractAndMerge = PVGeosBlockExtractAndMerge()
+ extractAndMergeFilter.SetInputConnection( self.GetInputConnection( 0, 0 ) )
+ extractAndMergeFilter.Update()
- Args:
- input (vtkMultiBlockDataSet): input multi block
+ self.volumeMesh.ShallowCopy( extractAndMergeFilter.GetOutputDataObject( 0 ) )
+ self.volumeMesh.Modified()
- Returns:
- bool: True if extraction and merge successfully eneded, False otherwise
- """
- filter: PVGeosBlockExtractAndMerge = PVGeosBlockExtractAndMerge()
- filter.SetInputConnection( self.GetInputConnection( 0, 0 ) )
- filter.SetLogger( self.m_logger )
- filter.Update()
-
- # recover output objects from PVGeosBlockExtractAndMerge
- self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
- self.m_surfaceMesh.ShallowCopy( filter.GetOutputDataObject( 1 ) )
- self.m_wells.ShallowCopy( filter.GetOutputDataObject( 2 ) )
- self.m_volumeMesh.Modified()
- self.m_surfaceMesh.Modified()
- self.m_wells.Modified()
- return True
-
- def computeAdditionalOutputsVolume( self: Self ) -> bool:
- """Compute geomechanical outputs on the volume mesh.
+ self.extractFault = extractAndMergeFilter.extractFault
+ if self.extractFault:
+ self.faultMesh.ShallowCopy( extractAndMergeFilter.GetOutputDataObject( 1 ) )
+ self.faultMesh.Modified()
- Returns:
- bool: True if calculation successfully eneded, False otherwise.
- """
- filter = PVGeomechanicsCalculator()
- filter.SetInputDataObject( self.m_volumeMesh ),
- filter.setComputeAdvancedProperties( self.getComputeAdvancedOutputs() )
- filter.setGrainBulkModulus( self.m_grainBulkModulus )
- filter.setSpecificDensity = ( self.m_specificDensity )
- filter.setRockCohesion = ( self.m_rockCohesion )
- filter.setFrictionAngle = ( self.m_frictionAngle )
- filter.Update()
- self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
- self.m_volumeMesh.Modified()
- return True
-
- def computeSurfaceGeomecanics( self: Self ) -> bool:
- """Compute surface geomechanics new attributes.
+ self.extractWell = extractAndMergeFilter.extractWell
+ if self.extractWell:
+ self.wellMesh.ShallowCopy( extractAndMergeFilter.GetOutputDataObject( 2 ) )
+ self.wellMesh.Modified()
- Returns:
- bool: True if calculation successfully eneded, False otherwise.
- """
- filter = PVSurfaceGeomechanics()
- filter.SetInputDataObject( self.m_surfaceMesh )
- filter.a01SetRockCohesion( self.getRockCohesion() )
- filter.a02SetFrictionAngle( self.getFrictionAngle() )
- filter.SetLogger( self.m_logger )
- filter.Update()
- self.m_surfaceMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
- self.m_surfaceMesh.Modified()
- return True
+ return
+
+ def applyPVGeomechanicsCalculator( self: Self ) -> None:
+ """Apply PVGeomechanicsCalculator."""
+ geomechanicsCalculatorPlugin = PVGeomechanicsCalculator()
+
+ geomechanicsCalculatorPlugin.SetInputDataObject( self.volumeMesh ),
+ geomechanicsCalculatorPlugin.setComputeAdvancedProperties( self.computeAdvancedProperties )
+ geomechanicsCalculatorPlugin.setGrainBulkModulus( self.grainBulkModulus )
+ geomechanicsCalculatorPlugin.setSpecificDensity( self.specificDensity )
+ geomechanicsCalculatorPlugin.setRockCohesion( self.rockCohesion )
+ geomechanicsCalculatorPlugin.setFrictionAngle( self.frictionAngle )
+ geomechanicsCalculatorPlugin.Update()
+
+ self.volumeMesh.ShallowCopy( geomechanicsCalculatorPlugin.GetOutputDataObject( 0 ) )
+ self.volumeMesh.Modified()
+
+ return
+
+ def applyPVSurfaceGeomechanics( self: Self ) -> None:
+ """Apply PVSurfaceGeomechanics."""
+ surfaceGeomechanicsPlugin = PVSurfaceGeomechanics()
+
+ surfaceGeomechanicsPlugin.SetInputDataObject( self.faultMesh )
+ surfaceGeomechanicsPlugin.a01SetRockCohesion( self.rockCohesion )
+ surfaceGeomechanicsPlugin.a02SetFrictionAngle( self.frictionAngle )
+ surfaceGeomechanicsPlugin.Update()
+
+ self.faultMesh.ShallowCopy( surfaceGeomechanicsPlugin.GetOutputDataObject( 0 ) )
+ self.faultMesh.Modified()
+
+ return
From 8876ea85c2c9585cb037e18786633e6bc5a02c4b Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 13 Nov 2025 10:56:37 +0100
Subject: [PATCH 56/62] Update friction angle unit to be coherent with surface
geomechanics plugin
---
.../pv/plugins/PVGeomechanicsCalculator.py | 22 ++++++++++---------
1 file changed, 12 insertions(+), 10 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py
index e0de4fbe4..5d870d758 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsCalculator.py
@@ -3,17 +3,19 @@
# SPDX-FileContributor: Martin Lemay, Romain Baville
# ruff: noqa: E402 # disable Module level import not at top of file
import sys
+import numpy as np
+
from pathlib import Path
from typing_extensions import Self
from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
- VTKPythonAlgorithmBase, smdomain, smproperty,
+ VTKPythonAlgorithmBase, smdomain, smproperty
) # 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,
+ VTKHandler
) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py
-from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, vtkMultiBlockDataSet
+from vtkmodules.vtkCommonDataModel import ( vtkUnstructuredGrid, vtkMultiBlockDataSet )
# update sys.path to load all GEOS Python Package dependencies
geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent
@@ -23,7 +25,7 @@
update_paths()
from geos.utils.PhysicalConstants import (
- DEFAULT_FRICTION_ANGLE_RAD,
+ DEFAULT_FRICTION_ANGLE_DEG,
DEFAULT_GRAIN_BULK_MODULUS,
DEFAULT_ROCK_COHESION,
WATER_DENSITY,
@@ -91,7 +93,7 @@ def __init__( self: Self ) -> None:
self.specificDensity: float = WATER_DENSITY
## For advanced properties
self.rockCohesion: float = DEFAULT_ROCK_COHESION
- self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD
+ self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_DEG
@smproperty.doublevector(
name="GrainBulkModulus",
@@ -190,23 +192,23 @@ def setRockCohesion( self: Self, rockCohesion: float ) -> None:
@smproperty.doublevector(
name="FrictionAngle",
- label="Friction Angle (rad)",
- default_values=DEFAULT_FRICTION_ANGLE_RAD,
+ label="Friction Angle (°)",
+ default_values=DEFAULT_FRICTION_ANGLE_DEG,
panel_visibility="default",
)
@smdomain.xml( """
Reference friction angle to compute critical pore pressure.
- The unit is rad. Default is 10.0 / 180.0 * np.pi rad.
+ The unit is °. Default is no friction case (i.e., 0.°).
""" )
def setFrictionAngle( self: Self, frictionAngle: float ) -> None:
"""Set friction angle.
Args:
- frictionAngle (float): Friction angle (rad).
+ frictionAngle (float): Friction angle (°).
"""
- self.frictionAngle = frictionAngle
+ self.frictionAngle = frictionAngle * np.pi / 180
self.Modified()
@smproperty.xml( """
From 3e741e9592a9338eeb476b36ffd49e5003e9066e Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 13 Nov 2025 15:43:57 +0100
Subject: [PATCH 57/62] Clean the log
---
.../post_processing/GeomechanicsCalculator.py | 3 +-
.../post_processing/GeosBlockExtractor.py | 23 +++++++-----
.../post_processing/GeosBlockMerge.py | 24 +++++++-----
.../geos/pv/plugins/PVGeomechanicsWorkflow.py | 13 ++++---
.../pv/plugins/PVGeosBlockExtractAndMerge.py | 4 +-
.../src/geos/pv/utils/workflowFunctions.py | 37 ++++++++++++-------
6 files changed, 64 insertions(+), 40 deletions(-)
diff --git a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py
index ffd8dd4a0..053404699 100644
--- a/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py
+++ b/geos-processing/src/geos/processing/post_processing/GeomechanicsCalculator.py
@@ -85,6 +85,7 @@
mesh: vtkUnstructuredGrid
computeAdvancedProperties: bool # optional, defaults to False
speHandler: bool # optional, defaults to False
+ loggerName: str # Defaults to "Geomechanics Calculator"
# Instantiate the filter
geomechanicsCalculatorFilter: GeomechanicsCalculator = GeomechanicsCalculator( mesh, computeAdvancedProperties, speHandler )
@@ -693,7 +694,7 @@ def __init__(
computeAdvancedProperties (bool, optional): True to compute advanced geomechanics properties, False otherwise.
Defaults to False.
loggerName (str, optional): Name of the filter logger.
- Defaults to "Geomechanics Calculator"
+ Defaults to "Geomechanics Calculator".
speHandler (bool, optional): True to use a specific handler, False to use the internal handler.
Defaults to False.
"""
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockExtractor.py b/geos-processing/src/geos/processing/post_processing/GeosBlockExtractor.py
index a4ff7cfdb..985959aa5 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockExtractor.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockExtractor.py
@@ -38,6 +38,7 @@
extractFault: bool # Defaults to False
extractWell: bool # Defaults to False
speHandler: bool # Defaults to False
+ loggerName: str # Defaults to "Geos Block Extractor"
# Instantiate the filter
geosBlockExtractor: GeosBlockExtractor = GeosBlockExtractor( geosMesh, extractFault, extractWell, speHandler )
@@ -56,8 +57,6 @@
geosDomainExtracted = geosBlockExtractor.extractedGeosDomain.well # For well domain
"""
-loggerTitle: str = "Geos Block Extractor"
-
class GeosExtractDomainBlock( vtkExtractBlock ):
@@ -149,6 +148,7 @@ def __init__(
extractFault: bool = False,
extractWell: bool = False,
speHandler: bool = False,
+ loggerName: str = "Geos Block Extractor",
) -> None:
"""Blocks from the ElementRegions from a GEOS output multiBlockDataset mesh.
@@ -160,6 +160,8 @@ def __init__(
Defaults to False.
speHandler (bool, optional): True to use a specific handler, False to use the internal handler.
Defaults to False.
+ loggerName (str, optional): Name of the filter logger.
+ Defaults to "Geos Block Extractor".
"""
self.geosMesh: vtkMultiBlockDataSet = geosMesh
self.extractedGeosDomain = self.ExtractedGeosDomain()
@@ -173,9 +175,9 @@ def __init__(
# Logger.
self.logger: Logger
if not speHandler:
- self.logger = getLogger( loggerTitle, True )
+ self.logger = getLogger( loggerName, True )
else:
- self.logger = logging.getLogger( loggerTitle )
+ self.logger = logging.getLogger( loggerName )
self.logger.setLevel( logging.INFO )
def setLoggerHandler( self: Self, handler: logging.Handler ) -> None:
@@ -201,15 +203,18 @@ def applyFilter( self: Self ) -> None:
extractGeosDomain: GeosExtractDomainBlock = GeosExtractDomainBlock()
extractGeosDomain.SetInputData( self.geosMesh )
+ domainNames: list = []
for domain in self.domainToExtract:
extractGeosDomain.RemoveAllIndices()
extractGeosDomain.AddGeosDomainName( domain )
extractGeosDomain.Update()
self.extractedGeosDomain.setExtractedDomain( domain, extractGeosDomain.GetOutput() )
+ domainNames.append( domain.value )
+
+ self.logger.info( f"The GEOS domain { domainNames } have been extracted." )
+ self.logger.info( f"The filter { self.logger.name } succeeded." )
- self.logger.info( "The filter succeeded." )
+ except ( ValueError, TypeError ) as e:
+ self.logger.error( f"The filter { self.logger.name } failed.\n{ e }." )
- except ValueError as ve:
- self.logger.error( f"The filter failed.\n{ ve }." )
- except TypeError as te:
- self.logger.error( f"The filter failed.\n{ te }." )
+ return
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index 1518c4dfd..5e5364efb 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -44,9 +44,10 @@
# Optional inputs.
convertFaultToSurface: bool # Defaults to False
speHandler: bool # Defaults to False
+ loggerName: str # Defaults to "GEOS Block Merge"
# Instantiate the filter
- mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( inputMesh, convertFaultToSurface, speHandler )
+ mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( inputMesh, convertFaultToSurface, speHandler, loggerName )
# Set the handler of yours (only if speHandler is True).
yourHandler: logging.Handler
@@ -59,8 +60,6 @@
outputMesh: vtkMultiBlockDataSet = mergeBlockFilter.getOutput()
"""
-loggerTitle: str = "GEOS Block Merge"
-
class GeosBlockMerge():
@@ -69,6 +68,7 @@ def __init__(
inputMesh: vtkMultiBlockDataSet,
convertFaultToSurface: bool = False,
speHandler: bool = False,
+ loggerName: str = "GEOS Block Merge",
) -> None:
"""VTK Filter that merge ranks of GEOS output mesh.
@@ -83,6 +83,8 @@ def __init__(
Defaults to False.
speHandler (bool, optional): True to use a specific handler, False to use the internal handler.
Defaults to False.
+ loggerName (str, optional): Name of the filter logger.
+ Defaults to "GEOS Block Merge".
"""
self.inputMesh: vtkMultiBlockDataSet = inputMesh
@@ -94,12 +96,12 @@ def __init__(
PhaseTypeEnum.FLUID.type: set(),
}
- # Logger.
+ # Logger
self.logger: Logger
if not speHandler:
- self.logger = getLogger( loggerTitle, True )
+ self.logger = getLogger( loggerName, True )
else:
- self.logger = logging.getLogger( loggerTitle )
+ self.logger = logging.getLogger( loggerName )
self.logger.setLevel( logging.INFO )
def setLoggerHandler( self: Self, handler: logging.Handler ) -> None:
@@ -161,8 +163,8 @@ def applyFilter( self: Self ) -> None:
# Convert the volume mesh to a surface mesh
if self.convertFaultToSurface:
if not isTriangulate( volumeMesh ):
- volumeMesh.ShallowCopy( triangulateMesh( volumeMesh ) )
- surfaceMesh: vtkPolyData = convertUnstructuredGridToPolyData( volumeMesh )
+ volumeMesh.ShallowCopy( triangulateMesh( volumeMesh, self.logger ) )
+ surfaceMesh: vtkPolyData = convertUnstructuredGridToPolyData( volumeMesh, self.logger )
surfaceMesh.ShallowCopy( computeNormals( surfaceMesh, logger=self.logger ) )
surfaceMesh.ShallowCopy( computeTangents( surfaceMesh, logger=self.logger ) )
# Add the merged block to the output mesh
@@ -170,9 +172,9 @@ def applyFilter( self: Self ) -> None:
else:
self.outputMesh.SetBlock( newIndex, volumeMesh )
- self.logger.info( "The filter succeeded." )
+ self.logger.info( f"The filter { self.logger.name } succeeded." )
except ( ValueError, TypeError, RuntimeError, AssertionError, VTKError ) as e:
- self.logger.critical( f"The filter failed.\n{ e }" )
+ self.logger.error( f"The filter { self.logger.name } failed.\n{ e }" )
return
@@ -197,6 +199,8 @@ def renameAttributes(
else:
renameAttribute( mesh, attributeName, newName, False )
+ return
+
def computePhaseNames( self: Self ) -> None:
"""Get the names of the phases in the mesh from Cell attributes."""
# All the phase attributes are on cells
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
index 542521ff7..791be20f1 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
@@ -108,10 +108,12 @@
class PVGeomechanicsWorkflow( VTKPythonAlgorithmBase ):
def __init__( self: Self ) -> None:
- """Paraview plugin to clean and add new outputs Geos output mesh.
+ """Paraview plugin to compute geomechanics properties on volume and on surface directly from the GEOS simulation output mesh.
- To apply in the case of output ".pvd" file contains Volume, Fault or
- Well elements.
+ This plugin is the combination of three other:
+ - First PVGeosBlockExtractAndMerge
+ - Secondly PVGeomechanicsCalculator
+ - Thirdly PVSurfaceGeomechanics (if the input mesh contains faults)
"""
super().__init__(
nInputPorts=1,
@@ -141,6 +143,8 @@ def __init__( self: Self ) -> None:
self.logger.setLevel( logging.INFO )
self.logger.addHandler( VTKHandler() )
+ self.logger.info( f"Apply plugin { self.logger.name }." )
+
@smproperty.doublevector(
name="GrainBulkModulus",
label="Grain bulk modulus (Pa)",
@@ -223,7 +227,7 @@ def setRockCohesion( self: Self, rockCohesion: float ) -> None:
@smdomain.xml( """
Reference friction angle to compute critical pore pressure.
- The unit is °. Default is no friction case (i.e., 0.°).
+ The unit is °. Default is an average friction angle (i.e., 10°).
""" )
def setFrictionAngle( self: Self, frictionAngle: float ) -> None:
@@ -320,7 +324,6 @@ def RequestData(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
- self.logger.info( f"Apply plugin { self.logger.name }." )
try:
self.volumeMesh = self.GetOutputData( outInfoVec, 0 )
self.faultMesh = self.GetOutputData( outInfoVec, 1 )
diff --git a/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py b/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
index abd6e4990..e43616154 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
@@ -118,6 +118,8 @@ def __init__( self: Self ) -> None:
self.logger.setLevel( logging.INFO )
self.logger.addHandler( VTKHandler() )
+ self.logger.info( f"Apply plugin { self.logger.name }." )
+
def RequestDataObject(
self: Self,
request: vtkInformation,
@@ -170,8 +172,6 @@ def RequestInformation(
Returns:
int: 1 if calculation successfully ended, 0 otherwise.
"""
- self.logger.info( f"Apply plugin { self.logger.name }." )
-
executive = self.GetExecutive()
inInfo = inInfoVec[ 0 ]
self.timeSteps = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() )
diff --git a/geos-pv/src/geos/pv/utils/workflowFunctions.py b/geos-pv/src/geos/pv/utils/workflowFunctions.py
index 39ae35ee7..6fe08e709 100644
--- a/geos-pv/src/geos/pv/utils/workflowFunctions.py
+++ b/geos-pv/src/geos/pv/utils/workflowFunctions.py
@@ -3,13 +3,12 @@
# SPDX-FileContributor: Romain Baville
# ruff: noqa: E402 # disable Module level import not at top of file
-
from geos.processing.post_processing.GeosBlockExtractor import GeosBlockExtractor
from geos.processing.post_processing.GeosBlockMerge import GeosBlockMerge
from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet
-from paraview.detail.loghandler import ( VTKHandler ) # type: ignore[import-not-found]
+from paraview.detail.loghandler import ( VTKHandler ) # type: ignore[import-not-found]
def doExtractAndMerge(
@@ -23,47 +22,59 @@ def doExtractAndMerge(
"""Apply block extraction and merge.
Args:
- mesh (vtkMultiBlockDataSet): Mesh to process.
- outputCells (vtkMultiBlockDataSet): Output volume mesh.
- outputFaults (vtkMultiBlockDataSet): Output surface mesh.
- outputWells (vtkMultiBlockDataSet): Output well mesh.
+ mesh (vtkMultiBlockDataSet): Mesh to process
+ outputCells (vtkMultiBlockDataSet): Output volume mesh
+ outputFaults (vtkMultiBlockDataSet): Output surface mesh
+ outputWells (vtkMultiBlockDataSet): Output well mesh
+ extractFault (bool, Optional): True if SurfaceElementRegion needs to be extracted, False otherwise.
+ extractWell (bool, Optional): True if WellElementRegion needs to be extracted, False otherwise.
"""
# Extract blocks
- blockExtractor: GeosBlockExtractor = GeosBlockExtractor( mesh, extractFault=extractFault, extractWell=extractWell, speHandler=True )
+ blockExtractor: GeosBlockExtractor = GeosBlockExtractor( mesh,
+ extractFault=extractFault,
+ extractWell=extractWell,
+ speHandler=True )
if not blockExtractor.logger.hasHandlers():
blockExtractor.setLoggerHandler( VTKHandler() )
blockExtractor.applyFilter()
# recover output objects from GeosBlockExtractor filter and merge internal blocks
volumeBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.volume
- outputCells.ShallowCopy( mergeBlocksFilter( volumeBlockExtracted, False ) )
+ outputCells.ShallowCopy( mergeBlocksFilter( volumeBlockExtracted, False, "Volume" ) )
outputCells.Modified()
if extractFault:
faultBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.fault
- outputFaults.ShallowCopy( mergeBlocksFilter( faultBlockExtracted, True ) )
+ outputFaults.ShallowCopy( mergeBlocksFilter( faultBlockExtracted, True, "Fault" ) )
outputFaults.Modified()
if extractWell:
wellBlockExtracted: vtkMultiBlockDataSet = blockExtractor.extractedGeosDomain.well
- outputWells.ShallowCopy( mergeBlocksFilter( wellBlockExtracted, False ) )
+ outputWells.ShallowCopy( mergeBlocksFilter( wellBlockExtracted, False, "Well" ) )
outputWells.Modified()
return
-def mergeBlocksFilter( mesh: vtkMultiBlockDataSet,
- convertSurfaces: bool = False, ) -> vtkMultiBlockDataSet:
+
+def mergeBlocksFilter(
+ mesh: vtkMultiBlockDataSet,
+ convertSurfaces: bool = False,
+ domainToMerge: str = "Volume",
+) -> vtkMultiBlockDataSet:
"""Apply vtk merge block filter on input multi block mesh.
Args:
mesh (vtkMultiBlockDataSet): Mesh to merge.
convertSurfaces (bool, optional): True to convert surface from vtkUnstructuredGrid to vtkPolyData.
Defaults to False.
+ domainToMerge (str, optional): The name of the GEOS domain processed.
+ Defaults to "Volume".
Returns:
vtkMultiBlockDataSet: Mesh composed of internal merged blocks.
"""
- mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( mesh, convertSurfaces, True )
+ loggerName = f"GEOS Block Merge for the domain { domainToMerge }."
+ mergeBlockFilter: GeosBlockMerge = GeosBlockMerge( mesh, convertSurfaces, True, loggerName )
if not mergeBlockFilter.logger.hasHandlers():
mergeBlockFilter.setLoggerHandler( VTKHandler() )
mergeBlockFilter.applyFilter()
From 7b54d8c37363b5ffef20054ce8560b326ba680b2 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 13 Nov 2025 15:48:18 +0100
Subject: [PATCH 58/62] Remove old geomechanics workflow plugins
---
docs/geos_pv_docs/processing.rst | 21 +-
.../plugins/PVGeomechanicsWorkflowVolume.py | 382 -----------------
.../PVGeomechanicsWorkflowVolumeSurface.py | 398 ------------------
.../PVGeomechanicsWorkflowVolumeWell.py | 396 -----------------
4 files changed, 1 insertion(+), 1196 deletions(-)
delete mode 100644 geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolume.py
delete mode 100644 geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurface.py
delete mode 100644 geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeWell.py
diff --git a/docs/geos_pv_docs/processing.rst b/docs/geos_pv_docs/processing.rst
index ff91f42dc..6b7c00ff3 100644
--- a/docs/geos_pv_docs/processing.rst
+++ b/docs/geos_pv_docs/processing.rst
@@ -53,36 +53,17 @@ PVGeosBlockExtractAndMerge plugin
.. automodule:: geos.pv.plugins.PVGeosBlockExtractAndMerge
-
Geomechanics workflows
++++++++++++++++++++++++
-PVGeomechanicsWorkflowVolume plugin
----------------------------------------------
-
-.. automodule:: geos.pv.plugins.PVGeomechanicsWorkflowVolume
-
-
-PVGeomechanicsWorkflowVolumeSurface plugin
-----------------------------------------------------
-
-.. automodule:: geos.pv.plugins.PVGeomechanicsWorkflowVolumeSurface
-
-
PVGeomechanicsWorkflow plugin
------------------------------
.. automodule:: geos.pv.plugins.PVGeomechanicsWorkflow
-PVGeomechanicsWorkflowVolumeWell plugin
--------------------------------------------------
-
-.. automodule:: geos.pv.plugins.PVGeomechanicsWorkflowVolumeWell
-
-
PVGeomechanicsCalculator plugin
---------------------------------------
-.. automodule:: geos.pv.plugins.PVGeomechanicsCalculator
\ No newline at end of file
+.. automodule:: geos.pv.plugins.PVGeomechanicsCalculator
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolume.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolume.py
deleted file mode 100644
index 7c550c37a..000000000
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolume.py
+++ /dev/null
@@ -1,382 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0
-# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Martin Lemay
-# ruff: noqa: E402 # disable Module level import not at top of file
-import sys
-from pathlib import Path
-
-import numpy as np
-from typing_extensions import Self
-from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector
-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
-
-update_paths()
-
-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 paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
- VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
-)
-
-from geos.pv.plugins.PVGeosBlockExtractAndMerge import PVGeosBlockExtractAndMerge
-from geos.pv.plugins.PVGeomechanicsCalculator import PVGeomechanicsCalculator
-
-__doc__ = """
-PVGeomechanicsWorkflowVolume is a Paraview plugin that execute multiple filters
-to clean GEOS outputs and compute additional geomechanical outputs on volume.
-
-Input and output types are vtkMultiBlockDataSet.
-
-This filter results in the volume mesh. If multiple regions were defined in
-the volume mesh, they are preserved as distinct blocks.
-
-To use it:
-
-* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVGeomechanicsWorkflowVolume.
-* Select the Geos output .pvd file loaded in Paraview.
-* Search and Apply PVGeomechanicsWorkflowVolume Filter.
-
-"""
-
-
-@smproxy.filter(
- name="PVGeomechanicsWorkflowVolume",
- label="Geos Geomechanics Workflow - Volume only",
-)
-@smhint.xml( """
-
-
- """ )
-@smproperty.input( name="Input", port_index=0 )
-@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
-class PVGeomechanicsWorkflowVolume( VTKPythonAlgorithmBase ):
-
- def __init__( self: Self ) -> None:
- """Paraview plugin to clean and add new outputs Geos output mesh.
-
- To apply in the case of output ".pvd" file contains only Volume element.
- """
- super().__init__(
- nInputPorts=1,
- nOutputPorts=1,
- inputType="vtkMultiBlockDataSet",
- outputType="vtkMultiBlockDataSet",
- )
-
- #: ouput volume mesh
- self.m_volumeMesh: vtkMultiBlockDataSet
-
- 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_RAD
-
- # set logger
- self.m_logger: Logger = getLogger( "Geomechanics Workflow Filter" )
-
- @smproperty.doublevector(
- name="GrainBulkModulus",
- label="Grain bulk modulus (Pa)",
- default_values=DEFAULT_GRAIN_BULK_MODULUS,
- panel_visibility="default",
- )
- @smdomain.xml( """
-
- Reference grain bulk modulus to compute Biot coefficient.
- The unit is Pa. Default is Quartz bulk modulus (i.e., 38GPa).
-
- """ )
- def b01SetGrainBulkModulus( self: Self, value: float ) -> None:
- """Set grain bulk modulus.
-
- Args:
- value (float): grain bulk modulus (Pa)
- """
- self.m_grainBulkModulus = value
- 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",
- label="Specific Density (kg/m3)",
- default_values=WATER_DENSITY,
- panel_visibility="default",
- )
- @smdomain.xml( """
-
- Reference density to compute specific gravity.
- The unit is kg/m3. Default is fresh water density (i.e., 1000 kg/m3).
-
- """ )
- def b02SetSpecificDensity( self: Self, value: float ) -> None:
- """Set specific density.
-
- Args:
- value (float): Reference specific density (kg/m3)
- """
- self.m_specificDensity = value
- self.Modified()
-
- def getSpecificDensity( self: Self ) -> float:
- """Access the specific density value.
-
- Returns:
- float: self.m_specificDensity.
- """
- return self.m_specificDensity
-
- @smproperty.xml( """
-
-
-
-
- """ )
- def b09GroupBasicOutputParameters( self: Self ) -> None:
- """Organize groups."""
- self.Modified()
-
- @smproperty.intvector(
- name="AdvancedOutputsUse",
- 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:
- """Set advanced output calculation option.
-
- Args:
- boolean (bool): if True advanced outputs are computed.
- """
- self.m_computeAdvancedOutputs = boolean
- 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",
- label="Rock Cohesion (Pa)",
- default_values=DEFAULT_ROCK_COHESION,
- 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:
- """Set rock cohesion.
-
- Args:
- value (float): rock cohesion (Pa)
- """
- self.m_rockCohesion = value
- 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,
- panel_visibility="default",
- )
- @smdomain.xml( """
-
- Reference friction angle to compute critical pore pressure.
- The unit is °. Default is 10°.
-
- """ )
- def d02SetFrictionAngle( self: Self, value: float ) -> None:
- """Set frition angle.
-
- Args:
- value (float): friction angle (°)
- """
- self.m_frictionAngle = value * np.pi / 180.0
- self.Modified()
-
- def getFrictionAngle( self: Self ) -> float:
- """Get friction angle in radian.
-
- Returns:
- float: friction angle.
- """
- return self.m_frictionAngle
-
- @smproperty.xml( """
-
-
-
-
-
-
-
- """ )
- def d09GroupAdvancedOutputParameters( self: Self ) -> None:
- """Organize groups."""
- self.Modified()
-
- 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(), "vtkMultiBlockDataSet" )
- return 1
-
- 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: vtkInformation = outInfoVec.GetInformationObject( 0 ) # noqa: F841
- return 1
-
- def RequestData(
- self: Self,
- request: vtkInformation,
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestData.
-
- Args:
- request (vtkInformation): request
- inInfoVec (list[vtkInformationVector]): input objects
- outInfoVec (vtkInformationVector): output objects
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- try:
- input: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
- self.m_volumeMesh = self.GetOutputData( outInfoVec, 0 )
-
- assert input is not None, "Input MultiBlockDataSet is null."
- assert self.m_volumeMesh is not None, "Output volume mesh is null."
-
- # 1. extract volume
- self.doExtractAndMerge()
- # 2. compute Geomechanical outputs in volume mesh
- self.computeAdditionalOutputsVolume()
-
- except AssertionError as e:
- mess: str = "Geomechanics workflow failed due to:"
- self.m_logger.error( mess )
- self.m_logger.error( str( e ) )
- return 0
- except Exception as e:
- mess1: str = "Geomechanics workflow failed due to:"
- self.m_logger.critical( mess1 )
- self.m_logger.critical( e, exc_info=True )
- return 0
- return 1
-
- def doExtractAndMerge( self: Self ) -> bool:
- """Apply block extraction and merge filter.
-
- Args:
- input (vtkMultiBlockDataSet): input multi block
-
- Returns:
- bool: True if extraction and merge successfully eneded, False otherwise
- """
- filter: PVGeosBlockExtractAndMerge = PVGeosBlockExtractAndMerge()
- filter.SetInputConnection( self.GetInputConnection( 0, 0 ) )
- filter.SetLogger( self.m_logger )
- filter.Update()
-
- # recover output objects from PVGeosBlockExtractAndMerge
- self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
- self.m_volumeMesh.Modified()
- return True
-
- def computeAdditionalOutputsVolume( self: Self ) -> bool:
- """Compute geomechanical outputs on the volume mesh.
-
- Returns:
- bool: True if calculation successfully eneded, False otherwise.
- """
- filter = PVGeomechanicsCalculator()
- filter.SetInputDataObject( self.m_volumeMesh ),
- filter.setComputeAdvancedProperties( self.getComputeAdvancedOutputs() )
- filter.setGrainBulkModulus( self.m_grainBulkModulus )
- filter.setSpecificDensity = ( self.m_specificDensity )
- filter.setRockCohesion = ( self.m_rockCohesion )
- filter.setFrictionAngle = ( self.m_frictionAngle )
- filter.Update()
- self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
- self.m_volumeMesh.Modified()
- return True
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurface.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurface.py
deleted file mode 100644
index d383c457a..000000000
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeSurface.py
+++ /dev/null
@@ -1,398 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0
-# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Martin Lemay
-# ruff: noqa: E402 # disable Module level import not at top of file
-import sys
-from pathlib import Path
-
-import numpy as np
-from typing_extensions import Self
-from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector
-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
-
-update_paths()
-
-from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
- VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
-)
-
-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.pv.plugins.PVGeosBlockExtractAndMerge import PVGeosBlockExtractAndMerge
-from geos.pv.plugins.PVGeomechanicsCalculator import PVGeomechanicsCalculator
-from geos.pv.plugins.PVSurfaceGeomechanics import PVSurfaceGeomechanics
-
-__doc__ = """
-PVGeomechanicsWorkflowVolumeSurface is a Paraview plugin that execute
-multiple filters to clean GEOS outputs and compute additional geomechanical
-outputs on volume and surface.
-
-Input and output types are vtkMultiBlockDataSet.
-
-This filter results in 2 output pipelines:
-
-* first pipeline contains the volume mesh. If multiple regions were defined in
- the volume mesh, they are preserved as distinct blocks.
-* second pipeline contains surfaces. If multiple surfaces were used, they are
- preserved as distinct blocks.
-
-To use it:
-
-* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVGeomechanicsWorkflowVolumeSurface.
-* Select the Geos output .pvd file loaded in Paraview.
-* Search and Apply PVGeomechanicsWorkflowVolumeSurface Filter.
-
-"""
-
-
-@smproxy.filter(
- name="PVGeomechanicsWorkflowVolumeSurface",
- label="Geos Geomechanics Workflow - Volume/Surface",
-)
-@smhint.xml( """
-
-
-
- """ )
-@smproperty.input( name="Input", port_index=0 )
-@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
-class PVGeomechanicsWorkflowVolumeSurface( VTKPythonAlgorithmBase ):
-
- def __init__( self: Self ) -> None:
- """Paraview plugin to clean and add new outputs Geos output mesh.
-
- To apply in the case of output ".pvd" file contains Volume and Fault
- elements.
- """
- super().__init__(
- nInputPorts=1,
- nOutputPorts=2,
- inputType="vtkMultiBlockDataSet",
- outputType="vtkMultiBlockDataSet",
- )
-
- #: ouput volume mesh
- self.m_volumeMesh: vtkMultiBlockDataSet
- #: output surface mesh
- self.m_surfaceMesh: vtkMultiBlockDataSet
-
- 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_RAD
-
- # set logger
- self.m_logger: Logger = getLogger( "Geomechanics Workflow Filter" )
-
- @smproperty.doublevector(
- name="GrainBulkModulus",
- label="Grain bulk modulus (Pa)",
- default_values=DEFAULT_GRAIN_BULK_MODULUS,
- panel_visibility="default",
- )
- @smdomain.xml( """
-
- Reference grain bulk modulus to compute Biot coefficient.
- The unit is Pa. Default is Quartz bulk modulus (i.e., 38GPa).
-
- """ )
- def b01SetGrainBulkModulus( self: Self, value: float ) -> None:
- """Set grain bulk modulus.
-
- Args:
- value (float): grain bulk modulus (Pa)
- """
- self.m_grainBulkModulus = value
- 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",
- label="Specific Density (kg/m3)",
- default_values=WATER_DENSITY,
- panel_visibility="default",
- )
- @smdomain.xml( """
-
- Reference density to compute specific gravity.
- The unit is kg/m3. Default is fresh water density (i.e., 1000 kg/m3).
-
- """ )
- def b02SetSpecificDensity( self: Self, value: float ) -> None:
- """Set specific density.
-
- Args:
- value (float): Reference specific density (kg/m3)
- """
- self.m_specificDensity = value
- self.Modified()
-
- def getSpecificDensity( self: Self ) -> float:
- """Access the specific density value.
-
- Returns:
- float: self.m_specificDensity.
- """
- return self.m_specificDensity
-
- @smproperty.xml( """
-
-
-
-
- """ )
- def b09GroupBasicOutputParameters( self: Self ) -> None:
- """Organize groups."""
- self.Modified()
-
- @smproperty.doublevector(
- name="RockCohesion",
- label="Rock Cohesion (Pa)",
- default_values=DEFAULT_ROCK_COHESION,
- 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:
- """Set rock cohesion.
-
- Args:
- value (float): rock cohesion (Pa)
- """
- self.m_rockCohesion = value
- 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,
- panel_visibility="default",
- )
- @smdomain.xml( """
-
- Reference friction angle to compute critical pore pressure.
- The unit is °. Default is 10°.
-
- """ )
- def d02SetFrictionAngle( self: Self, value: float ) -> None:
- """Set frition angle.
-
- Args:
- value (float): friction angle (°)
- """
- self.m_frictionAngle = value * np.pi / 180.0
- self.Modified()
-
- def getFrictionAngle( self: Self ) -> float:
- """Get friction angle in radian.
-
- Returns:
- float: friction angle.
- """
- return self.m_frictionAngle
-
- @smproperty.xml( """
-
-
-
-
- """ )
- def d09GroupAdvancedOutputParameters( self: Self ) -> None:
- """Organize groups."""
- self.Modified()
-
- @smproperty.intvector(
- name="AdvancedOutputsUse",
- label="Compute advanced geomechanical volume outputs",
- default_values=0,
- panel_visibility="default",
- )
- @smdomain.xml( """
-
-
- Check to compute advanced geomechanical outputs including
- reservoir stress paths and fracture indexes.
-
- """ )
- def e01SetAdvancedOutputs( self: Self, boolean: bool ) -> None:
- """Set advanced output calculation option.
-
- Args:
- boolean (bool): if True advanced outputs are computed.
- """
- self.m_computeAdvancedOutputs = boolean
- self.Modified()
-
- def getComputeAdvancedOutputs( self: Self ) -> float:
- """Access the advanced outputs option.
-
- Returns:
- float: self.m_computeAdvancedOutputs.
- """
- return self.m_computeAdvancedOutputs
-
- 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(), "vtkMultiBlockDataSet" )
- return 1
-
- 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: vtkInformation = outInfoVec.GetInformationObject( 0 ) # noqa: F841
- return 1
-
- def RequestData(
- self: Self,
- request: vtkInformation,
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestData.
-
- Args:
- request (vtkInformation): request
- inInfoVec (list[vtkInformationVector]): input objects
- outInfoVec (vtkInformationVector): output objects
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- try:
- input: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
- self.m_volumeMesh = self.GetOutputData( outInfoVec, 0 )
- self.m_surfaceMesh = self.GetOutputData( outInfoVec, 1 )
-
- assert input is not None, "Input MultiBlockDataSet is null."
- assert self.m_volumeMesh is not None, "Output volume mesh is null."
- assert self.m_surfaceMesh is not None, "Output surface mesh is null."
-
- # 1. extract volume/surface
- self.doExtractAndMerge()
- # 2. compute Geomechanical outputs in volume mesh
- self.computeAdditionalOutputsVolume()
- # 3. compute geomechanical outputs on surface mesh
- self.computeSurfaceGeomecanics()
-
- except AssertionError as e:
- mess: str = "Geomechanics workflow failed due to:"
- self.m_logger.error( mess )
- self.m_logger.error( str( e ) )
- return 0
- except Exception as e:
- mess1: str = "Geomechanics workflow failed due to:"
- self.m_logger.critical( mess1 )
- self.m_logger.critical( e, exc_info=True )
- return 0
- return 1
-
- def doExtractAndMerge( self: Self ) -> bool:
- """Apply block extraction and merge filter.
-
- Args:
- input (vtkMultiBlockDataSet): input multi block
-
- Returns:
- bool: True if extraction and merge successfully eneded, False otherwise
- """
- filter: PVGeosBlockExtractAndMerge = PVGeosBlockExtractAndMerge()
- filter.SetInputConnection( self.GetInputConnection( 0, 0 ) )
- filter.SetLogger( self.m_logger )
- filter.Update()
-
- # recover output objects from PVGeosBlockExtractAndMerge
- self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
- self.m_surfaceMesh.ShallowCopy( filter.GetOutputDataObject( 1 ) )
- self.m_volumeMesh.Modified()
- self.m_surfaceMesh.Modified()
- return True
-
- def computeAdditionalOutputsVolume( self: Self ) -> bool:
- """Compute geomechanical outputs on the volume mesh.
-
- Returns:
- bool: True if calculation successfully eneded, False otherwise.
- """
- filter = PVGeomechanicsCalculator()
- filter.SetInputDataObject( self.m_volumeMesh ),
- filter.setComputeAdvancedProperties( self.getComputeAdvancedOutputs() )
- filter.setGrainBulkModulus( self.m_grainBulkModulus )
- filter.setSpecificDensity = ( self.m_specificDensity )
- filter.setRockCohesion = ( self.m_rockCohesion )
- filter.setFrictionAngle = ( self.m_frictionAngle )
- filter.Update()
- self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
- self.m_volumeMesh.Modified()
- return True
-
- def computeSurfaceGeomecanics( self: Self ) -> bool:
- """Compute surface geomechanics new attributes.
-
- Returns:
- bool: True if calculation successfully eneded, False otherwise.
- """
- filter = PVSurfaceGeomechanics()
- filter.SetInputDataObject( self.m_surfaceMesh )
- filter.a01SetRockCohesion( self.getRockCohesion() )
- filter.a02SetFrictionAngle( self.getFrictionAngle() )
- filter.SetLogger( self.m_logger )
- filter.Update()
- self.m_surfaceMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
- self.m_surfaceMesh.Modified()
- return True
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeWell.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeWell.py
deleted file mode 100644
index 44aeb977d..000000000
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflowVolumeWell.py
+++ /dev/null
@@ -1,396 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0
-# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
-# SPDX-FileContributor: Martin Lemay
-# ruff: noqa: E402 # disable Module level import not at top of file
-import sys
-from pathlib import Path
-
-import numpy as np
-from typing_extensions import Self
-from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector
-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
-
-update_paths()
-
-from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
- VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy,
-)
-
-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.pv.plugins.PVGeosBlockExtractAndMerge import PVGeosBlockExtractAndMerge
-from geos.pv.plugins.PVGeomechanicsCalculator import PVGeomechanicsCalculator
-
-__doc__ = """
-PVGeomechanicsWorkflowVolumeWell is a Paraview plugin that execute
-multiple filters to clean GEOS outputs and compute additional geomechanical
-outputs on volume and wells.
-
-Input and output types are vtkMultiBlockDataSet.
-
-This filter results in 2 output pipelines:
-
-* first pipeline contains the volume mesh. If multiple regions were defined in
- the volume mesh, they are preserved as distinct blocks.
-* second pipeline contains wells. If multiple wells were used, they are preserved
- as distinct blocks.
-
-To use it:
-
-* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVGeomechanicsWorkflowVolumeWell.
-* Select the Geos output .pvd file loaded in Paraview.
-* Search and Apply PVGeomechanicsWorkflowVolumeWell Filter.
-
-"""
-
-
-@smproxy.filter(
- name="PVGeomechanicsWorkflowVolumeWell",
- label="Geos Geomechanics Workflow - Volume/Well",
-)
-@smhint.xml( """
-
-
-
- """ )
-@smproperty.input( name="Input", port_index=0 )
-@smdomain.datatype( dataTypes=[ "vtkMultiBlockDataSet" ], composite_data_supported=True )
-class PVGeomechanicsWorkflowVolumeWell( VTKPythonAlgorithmBase ):
-
- def __init__( self: Self ) -> None:
- """Paraview plugin to clean and add new outputs Geos output mesh.
-
- To apply in the case of output ".pvd" file contains Volume and
- Well elements.
- """
- super().__init__(
- nInputPorts=1,
- nOutputPorts=2,
- inputType="vtkMultiBlockDataSet",
- outputType="vtkMultiBlockDataSet",
- )
-
- #: ouput volume mesh
- self.m_volumeMesh: vtkMultiBlockDataSet
- #: output wells
- self.m_wells: vtkMultiBlockDataSet
-
- 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_RAD
-
- # set logger
- self.m_logger: Logger = getLogger( "Geomechanics Workflow Filter" )
-
- @smproperty.doublevector(
- name="GrainBulkModulus",
- label="Grain bulk modulus (Pa)",
- default_values=DEFAULT_GRAIN_BULK_MODULUS,
- panel_visibility="default",
- )
- @smdomain.xml( """
-
- Reference grain bulk modulus to compute Biot coefficient.
- The unit is Pa. Default is Quartz bulk modulus (i.e., 38GPa).
-
- """ )
- def b01SetGrainBulkModulus( self: Self, value: float ) -> None:
- """Set grain bulk modulus.
-
- Args:
- value (float): grain bulk modulus (Pa)
- """
- self.m_grainBulkModulus = value
- 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",
- label="Specific Density (kg/m3)",
- default_values=WATER_DENSITY,
- panel_visibility="default",
- )
- @smdomain.xml( """
-
- Reference density to compute specific gravity.
- The unit is kg/m3. Default is fresh water density (i.e., 1000 kg/m3).
-
- """ )
- def b02SetSpecificDensity( self: Self, value: float ) -> None:
- """Set specific density.
-
- Args:
- value (float): Reference specific density (kg/m3)
- """
- self.m_specificDensity = value
- self.Modified()
-
- def getSpecificDensity( self: Self ) -> float:
- """Access the specific density value.
-
- Returns:
- float: self.m_specificDensity.
- """
- return self.m_specificDensity
-
- @smproperty.xml( """
-
-
-
-
- """ )
- def b09GroupBasicOutputParameters( self: Self ) -> None:
- """Organize groups."""
- self.Modified()
-
- @smproperty.intvector(
- name="AdvancedOutputsUse",
- 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:
- """Set advanced output calculation option.
-
- Args:
- boolean (bool): if True advanced outputs are computed.
- """
- self.m_computeAdvancedOutputs = boolean
- 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",
- label="Rock Cohesion (Pa)",
- default_values=DEFAULT_ROCK_COHESION,
- 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:
- """Set rock cohesion.
-
- Args:
- value (float): rock cohesion (Pa)
- """
- self.m_rockCohesion = value
- 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,
- panel_visibility="default",
- )
- @smdomain.xml( """
-
- Reference friction angle to compute critical pore pressure.
- The unit is °. Default is 10°.
-
- """ )
- def d02SetFrictionAngle( self: Self, value: float ) -> None:
- """Set frition angle.
-
- Args:
- value (float): friction angle (°)
- """
- self.m_frictionAngle = value * np.pi / 180.0
- self.Modified()
-
- def getFrictionAngle( self: Self ) -> float:
- """Get friction angle in radian.
-
- Returns:
- float: friction angle.
- """
- return self.m_frictionAngle
-
- @smproperty.xml( """
-
-
-
-
-
-
-
- """ )
- def d09GroupAdvancedOutputParameters( self: Self ) -> None:
- """Organize groups."""
- self.Modified()
-
- 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(), "vtkMultiBlockDataSet" )
- return 1
-
- 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: vtkInformation = outInfoVec.GetInformationObject( 0 ) # noqa: F841
- return 1
-
- def RequestData(
- self: Self,
- request: vtkInformation,
- inInfoVec: list[ vtkInformationVector ],
- outInfoVec: vtkInformationVector,
- ) -> int:
- """Inherited from VTKPythonAlgorithmBase::RequestData.
-
- Args:
- request (vtkInformation): request
- inInfoVec (list[vtkInformationVector]): input objects
- outInfoVec (vtkInformationVector): output objects
-
- Returns:
- int: 1 if calculation successfully ended, 0 otherwise.
- """
- try:
- input: vtkMultiBlockDataSet = vtkMultiBlockDataSet.GetData( inInfoVec[ 0 ] )
- self.m_volumeMesh = self.GetOutputData( outInfoVec, 0 )
- self.m_wells = self.GetOutputData( outInfoVec, 1 )
-
- assert input is not None, "Input MultiBlockDataSet is null."
- assert self.m_volumeMesh is not None, "Output volume mesh is null."
- assert self.m_wells is not None, "Output well mesh is null."
-
- # 1. extract volume/wells
- self.doExtractAndMerge()
- # 2. compute Geomechanical outputs in volume mesh
- self.computeAdditionalOutputsVolume()
-
- except AssertionError as e:
- mess: str = "Geomechanics workflow failed due to:"
- self.m_logger.error( mess )
- self.m_logger.error( str( e ) )
- return 0
- except Exception as e:
- mess1: str = "Geomechanics workflow failed due to:"
- self.m_logger.critical( mess1 )
- self.m_logger.critical( e, exc_info=True )
- return 0
- return 1
-
- def doExtractAndMerge( self: Self ) -> bool:
- """Apply block extraction and merge filter.
-
- Args:
- input (vtkMultiBlockDataSet): input multi block
-
- Returns:
- bool: True if extraction and merge successfully eneded, False otherwise
- """
- filter: PVGeosBlockExtractAndMerge = PVGeosBlockExtractAndMerge()
- filter.SetInputConnection( self.GetInputConnection( 0, 0 ) )
- filter.SetLogger( self.m_logger )
- filter.Update()
-
- # recover output objects from PVGeosBlockExtractAndMerge
- self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
- self.m_wells.ShallowCopy( filter.GetOutputDataObject( 1 ) )
- self.m_volumeMesh.Modified()
- self.m_wells.Modified()
- return True
-
- def computeAdditionalOutputsVolume( self: Self ) -> bool:
- """Compute geomechanical outputs on the volume mesh.
-
- Returns:
- bool: True if calculation successfully eneded, False otherwise.
- """
- filter = PVGeomechanicsCalculator()
- filter.SetInputDataObject( self.m_volumeMesh ),
- filter.setComputeAdvancedProperties( self.getComputeAdvancedOutputs() )
- filter.setGrainBulkModulus( self.m_grainBulkModulus )
- filter.setSpecificDensity = ( self.m_specificDensity )
- filter.setRockCohesion = ( self.m_rockCohesion )
- filter.setFrictionAngle = ( self.m_frictionAngle )
- filter.Update()
- self.m_volumeMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) )
- self.m_volumeMesh.Modified()
- return True
From 8a7b9a68c5a3c38717d176d6f92cff421cfe3b2d Mon Sep 17 00:00:00 2001
From: Romain Baville <126683264+RomainBaville@users.noreply.github.com>
Date: Thu, 13 Nov 2025 16:21:06 +0100
Subject: [PATCH 59/62] Update the doc
Co-authored-by: paloma-martinez <104762252+paloma-martinez@users.noreply.github.com>
---
geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py b/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
index abd6e4990..beea5400f 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
@@ -37,13 +37,13 @@
__doc__ = """
PVGeosBlockExtractAndMerge is a Paraview plugin processing the input mesh at the current time in two steps:
- - First extracts domains (volume, fault and well) from a GEOS output multiBlockDataSet mesh
- - Second Acts on each region of a GEOS output domain (volume, fault, wells) to:
+ 1. Extraction of domains (volume, fault and well) from a GEOS output multiBlockDataSet mesh
+ 2. Actions on each region of a GEOS output domain (volume, fault, wells) to:
* Merge Ranks
* Identify "Fluids" and "Rock" phases
* Rename "Rock" attributes depending on the phase they refer to for more clarity
* Convert volume meshes to surface if needed
- * Copy "geomechanics" attributes from the initial timestep to the current one if their exist
+ * Copy "geomechanics" attributes from the initial timestep to the current one if they exist
This filter results in 3 output pipelines with the vtkMultiBlockDataSet:
- "Volume" contains the volume domain
From f4c7bf5b3683c58f299ce237dbbe17624aae5fc4 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Thu, 13 Nov 2025 16:28:38 +0100
Subject: [PATCH 60/62] Update the doc
---
.../src/geos/pv/plugins/PVGeomechanicsWorkflow.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
index 791be20f1..992a73020 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeomechanicsWorkflow.py
@@ -36,18 +36,18 @@
__doc__ = """
PVGeomechanicsWorkflow is a Paraview plugin that executes multiple plugins:
- - First PVGeosBlockExtractAndMerge
- - Secondly PVGeomechanicsCalculator
- - Thirdly PVSurfaceGeomechanics (if the input mesh contains faults)
+ 1. PVGeosBlockExtractAndMerge
+ 2. PVGeomechanicsCalculator
+ 3. PVSurfaceGeomechanics (if the input mesh contains faults)
PVGeosBlockExtractAndMerge is a Paraview plugin processing the input mesh at the current time in two steps:
- - First extracts domains (volume, fault and well) from a GEOS output multiBlockDataSet mesh
- - Second Acts on each region of a GEOS output domain (volume, fault, wells) to:
+ 1. Extraction of domains (volume, fault and well) from a GEOS output multiBlockDataSet mesh
+ 2. Actions on each region of a GEOS output domain (volume, fault, wells) to:
* Merge Ranks
* Identify "Fluids" and "Rock" phases
* Rename "Rock" attributes depending on the phase they refer to for more clarity
* Convert volume meshes to surface if needed
- * Copy "geomechanics" attributes from the initial timestep to the current one if their exist
+ * Copy "geomechanics" attributes from the initial timestep to the current one if they exist
PVGeomechanicsCalculator is a paraview plugin that allows to compute basic and advanced geomechanics properties from existing ones in the mesh. This is donne on each block of the volume mesh.
From e4c334975ff4e1eb72d5dee67fdde8cf4bc4ee82 Mon Sep 17 00:00:00 2001
From: Romain Baville
Date: Fri, 14 Nov 2025 11:22:53 +0100
Subject: [PATCH 61/62] clean vtk error logging
---
.../src/geos/mesh/utils/genericHelpers.py | 104 ++++++++++--------
.../post_processing/GeosBlockMerge.py | 3 +
.../post_processing/SurfaceGeomechanics.py | 2 +-
.../pv/plugins/PVGeosBlockExtractAndMerge.py | 1 +
geos-utils/src/geos/utils/Logger.py | 2 +-
5 files changed, 67 insertions(+), 45 deletions(-)
diff --git a/geos-mesh/src/geos/mesh/utils/genericHelpers.py b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
index c766173ab..8df3431bd 100644
--- a/geos-mesh/src/geos/mesh/utils/genericHelpers.py
+++ b/geos-mesh/src/geos/mesh/utils/genericHelpers.py
@@ -62,16 +62,17 @@ def triangulateMesh(
Returns:
vtkUnstructuredGrid: Triangulated mesh
"""
+ vtkErrorLogger: Logger
if logger is None:
- logger = getLogger( "Triangulate", True )
- # Creation of a child logger to deal with VTKErrors without polluting parent logger
- vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger", True )
- vtkErrorLogger.propagate = False
+ vtkErrorLogger = getLogger( "Triangulate Mesh vtkError Logger", True )
+ else:
+ vtkErrorLogger = logging.getLogger( f"{ logger.name } vtkError Logger" )
+ vtkErrorLogger.setLevel( logging.INFO )
+ vtkErrorLogger.addHandler( logger.handlers[ 0 ] )
+ vtkErrorLogger.propagate = False
vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR )
-
vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error
- vtkErrorLogger.setLevel( logging.DEBUG )
with VTKCaptureLog() as capturedLog:
triangulateMeshFilter: vtkDataSetTriangleFilter = vtkDataSetTriangleFilter()
@@ -81,7 +82,8 @@ def triangulateMesh(
capturedLog.seek( 0 )
captured = capturedLog.read().decode()
- vtkErrorLogger.debug( captured.strip() )
+ if captured != "":
+ vtkErrorLogger.error( captured.strip() )
triangulatedMesh: vtkUnstructuredGrid = triangulateMeshFilter.GetOutput()
@@ -107,16 +109,17 @@ def convertUnstructuredGridToPolyData(
Returns:
vtkPolyData: Extracted surface
"""
+ vtkErrorLogger: Logger
if logger is None:
- logger = getLogger( "ConvertVtkUnstructuredGridToVtkPolyData.", True )
- # Creation of a child logger to deal with VTKErrors without polluting parent logger
- vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger", True )
- vtkErrorLogger.propagate = False
+ vtkErrorLogger = getLogger( "Convert vtkUnstructuredGrid To vtkPolyData vtkError Logger", True )
+ else:
+ vtkErrorLogger = logging.getLogger( f"{ logger.name } vtkError Logger" )
+ vtkErrorLogger.setLevel( logging.INFO )
+ vtkErrorLogger.addHandler( logger.handlers[ 0 ] )
+ vtkErrorLogger.propagate = False
vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR )
-
vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error
- vtkErrorLogger.setLevel( logging.DEBUG )
with VTKCaptureLog() as capturedLog:
extractSurfaceFilter: vtkDataSetSurfaceFilter = vtkDataSetSurfaceFilter()
@@ -131,7 +134,8 @@ def convertUnstructuredGridToPolyData(
capturedLog.seek( 0 )
captured = capturedLog.read().decode()
- vtkErrorLogger.debug( captured.strip() )
+ if captured != "":
+ vtkErrorLogger.error( captured.strip() )
extractedSurface: vtkPolyData = extractSurfaceFilter.GetOutput()
@@ -460,8 +464,8 @@ def getTangentsVectors( surface: vtkPolyData ) -> Tuple[ npt.NDArray[ np.float64
try:
tangents1 = vtk_to_numpy( vtkTangents )
except AttributeError as err:
- print( "No tangential attribute found in the mesh. Use the computeTangents function beforehand." )
- raise VTKError( err ) from err
+ context: str = f"No tangential attribute found in the mesh. Use the computeTangents function beforehand.\n{ err }"
+ raise VTKError( context ) from err
else:
# Compute second tangential component
normals: npt.NDArray[ np.float64 ] = getNormalVectors( surface )
@@ -471,22 +475,30 @@ def getTangentsVectors( surface: vtkPolyData ) -> Tuple[ npt.NDArray[ np.float64
return ( tangents1, tangents2 )
-def getLocalBasisVectors( surface: vtkPolyData ) -> npt.NDArray[ np.float64 ]:
+def getLocalBasisVectors(
+ surface: vtkPolyData,
+ logger: Union[ Logger, None ] = None,
+) -> npt.NDArray[ np.float64 ]:
"""Return the local basis vectors for all cells of the input surface.
Args:
surface(vtkPolydata): The input surface.
+ logger (Union[Logger, None], optional): A logger to manage the output messages.
+ Defaults to None, an internal logger is used.
Returns:
npt.NDArray[np.float64]: Array with normal, tangential 1 and tangential 2 vectors.
"""
+ if logger is None:
+ logger = getLogger( "getLocalBasisVectors", True )
+
try:
normals: npt.NDArray[ np.float64 ] = getNormalVectors( surface )
surfaceWithNormals: vtkPolyData = surface
# ValueError raised if no normals found in the mesh
except ValueError:
# In that case, the normals are computed.
- surfaceWithNormals = computeNormals( surface )
+ surfaceWithNormals = computeNormals( surface, logger )
normals = getNormalVectors( surfaceWithNormals )
# Tangents require normals to be present in the mesh
@@ -495,7 +507,7 @@ def getLocalBasisVectors( surface: vtkPolyData ) -> npt.NDArray[ np.float64 ]:
npt.NDArray[ np.float64 ] ] = getTangentsVectors( surfaceWithNormals )
# If no tangents is present in the mesh, they are computed on that surface
except VTKError:
- surfaceWithTangents: vtkPolyData = computeTangents( surfaceWithNormals )
+ surfaceWithTangents: vtkPolyData = computeTangents( surfaceWithNormals, logger )
tangents = getTangentsVectors( surfaceWithTangents )
return np.array( ( normals, *tangents ) )
@@ -515,16 +527,17 @@ def computeNormals(
Returns:
vtkPolyData: The surface with normal attribute.
"""
+ vtkErrorLogger: Logger
if logger is None:
- logger = getLogger( "computeSurfaceNormals" )
- # Creation of a child logger to deal with VTKErrors without polluting parent logger
- vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger" )
- vtkErrorLogger.propagate = False
+ vtkErrorLogger = getLogger( "Compute Surface Normals vtkError Logger", True )
+ else:
+ vtkErrorLogger = logging.getLogger( f"{ logger.name } vtkError Logger" )
+ vtkErrorLogger.setLevel( logging.INFO )
+ vtkErrorLogger.addHandler( logger.handlers[ 0 ] )
+ vtkErrorLogger.propagate = False
vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR )
-
vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error
- vtkErrorLogger.setLevel( logging.DEBUG )
with VTKCaptureLog() as capturedLog:
normalFilter: vtkPolyDataNormals = vtkPolyDataNormals()
@@ -536,7 +549,8 @@ def computeNormals(
capturedLog.seek( 0 )
captured = capturedLog.read().decode()
- vtkErrorLogger.debug( captured.strip() )
+ if captured != "":
+ vtkErrorLogger.error( captured.strip() )
outputSurface = normalFilter.GetOutput()
@@ -562,22 +576,23 @@ def computeTangents(
Returns:
vtkPolyData: The surface with tangent attribute
"""
- # need to compute texture coordinates required for tangent calculation
- surface1: vtkPolyData = computeSurfaceTextureCoordinates( triangulatedSurface )
+ # Need to compute texture coordinates required for tangent calculation
+ surface1: vtkPolyData = computeSurfaceTextureCoordinates( triangulatedSurface, logger )
# TODO: triangulate the surface before computation of the tangents if needed.
- # compute tangents
+ # Compute tangents
+ vtkErrorLogger: Logger
if logger is None:
- logger = getLogger( "computeSurfaceTangents" )
- # Creation of a child logger to deal with VTKErrors without polluting parent logger
- vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger" )
- vtkErrorLogger.propagate = False
+ vtkErrorLogger = getLogger( "Compute Surface Tangents vtkError Logger", True )
+ else:
+ vtkErrorLogger = logging.getLogger( f"{ logger.name } vtkError Logger" )
+ vtkErrorLogger.setLevel( logging.INFO )
+ vtkErrorLogger.addHandler( logger.handlers[ 0 ] )
+ vtkErrorLogger.propagate = False
vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR )
-
vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error
- vtkErrorLogger.setLevel( logging.DEBUG )
with VTKCaptureLog() as capturedLog:
@@ -591,7 +606,8 @@ def computeTangents(
capturedLog.seek( 0 )
captured = capturedLog.read().decode()
- vtkErrorLogger.debug( captured.strip() )
+ if captured != "":
+ vtkErrorLogger.error( captured.strip() )
if surfaceOut is None:
raise VTKError( "Something went wrong in VTK calculation." )
@@ -623,16 +639,17 @@ def computeSurfaceTextureCoordinates(
vtkPolyData: The input surface with generated texture map.
"""
# Need to compute texture coordinates required for tangent calculation
+ vtkErrorLogger: Logger
if logger is None:
- logger = getLogger( "computeSurfaceTextureCoordinates" )
- # Creation of a child logger to deal with VTKErrors without polluting parent logger
- vtkErrorLogger: Logger = getLogger( f"{logger.name}.vtkErrorLogger" )
- vtkErrorLogger.propagate = False
+ vtkErrorLogger = getLogger( "Compute Surface Texture Coordinates vtkError Logger", True )
+ else:
+ vtkErrorLogger = logging.getLogger( f"{ logger.name } vtkError Logger" )
+ vtkErrorLogger.setLevel( logging.INFO )
+ vtkErrorLogger.addHandler( logger.handlers[ 0 ] )
+ vtkErrorLogger.propagate = False
vtkLogger.SetStderrVerbosity( vtkLogger.VERBOSITY_ERROR )
-
vtkErrorLogger.addFilter( RegexExceptionFilter() ) # will raise VTKError if captured VTK Error
- vtkErrorLogger.setLevel( logging.DEBUG )
with VTKCaptureLog() as capturedLog:
@@ -644,6 +661,7 @@ def computeSurfaceTextureCoordinates(
capturedLog.seek( 0 )
captured = capturedLog.read().decode()
- vtkErrorLogger.debug( captured.strip() )
+ if captured != "":
+ vtkErrorLogger.error( captured.strip() )
return textureFilter.GetOutput()
diff --git a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
index 5e5364efb..4f4ba224d 100644
--- a/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
+++ b/geos-processing/src/geos/processing/post_processing/GeosBlockMerge.py
@@ -89,6 +89,7 @@ def __init__(
"""
self.inputMesh: vtkMultiBlockDataSet = inputMesh
self.convertFaultToSurface: bool = convertFaultToSurface
+ self.handler: None | logging.Handler = None
self.outputMesh: vtkMultiBlockDataSet = vtkMultiBlockDataSet()
self.phaseNameDict: dict[ str, set[ str ] ] = {
@@ -103,6 +104,7 @@ def __init__(
else:
self.logger = logging.getLogger( loggerName )
self.logger.setLevel( logging.INFO )
+ self.logger.propagate = False
def setLoggerHandler( self: Self, handler: logging.Handler ) -> None:
"""Set a specific handler for the filter logger.
@@ -113,6 +115,7 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None:
handler (logging.Handler): The handler to add.
"""
if not self.logger.hasHandlers():
+ self.handler = handler
self.logger.addHandler( handler )
else:
self.logger.warning(
diff --git a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
index 90320e826..62fcd013a 100644
--- a/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
+++ b/geos-processing/src/geos/processing/post_processing/SurfaceGeomechanics.py
@@ -351,7 +351,7 @@ def __computeXYZCoordinates(
attrXYZ: npt.NDArray[ np.float64 ] = np.full_like( attrArray, np.nan )
# Get all local basis vectors
- localBasis: npt.NDArray[ np.float64 ] = getLocalBasisVectors( self.outputMesh )
+ localBasis: npt.NDArray[ np.float64 ] = getLocalBasisVectors( self.outputMesh, self.logger )
for i, cellAttribute in enumerate( attrArray ):
if len( cellAttribute ) not in ( 3, 6, 9 ):
diff --git a/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py b/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
index a6058001c..c779c5eb1 100644
--- a/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
+++ b/geos-pv/src/geos/pv/plugins/PVGeosBlockExtractAndMerge.py
@@ -117,6 +117,7 @@ def __init__( self: Self ) -> None:
self.logger = logging.getLogger( loggerTitle )
self.logger.setLevel( logging.INFO )
self.logger.addHandler( VTKHandler() )
+ self.logger.propagate = False
self.logger.info( f"Apply plugin { self.logger.name }." )
diff --git a/geos-utils/src/geos/utils/Logger.py b/geos-utils/src/geos/utils/Logger.py
index 473f98f2a..099ce9077 100644
--- a/geos-utils/src/geos/utils/Logger.py
+++ b/geos-utils/src/geos/utils/Logger.py
@@ -275,7 +275,7 @@ def getLogger( title: str, use_color: bool = False ) -> Logger:
"""
logger = logging.getLogger( title )
# Only configure the logger (add handlers, set level) if it hasn't been configured before.
- if not logger.hasHandlers(): # More Pythonic way to check if logger.handlers is empty
+ if len( logger.handlers ) == 0:
logger.setLevel( INFO ) # Set the desired default level for this logger
# Create and add the stream handler
ch = logging.StreamHandler()
From 68a702e29cba54ec0cc12b202bce701e017daf79 Mon Sep 17 00:00:00 2001
From: Romain Baville <126683264+RomainBaville@users.noreply.github.com>
Date: Fri, 14 Nov 2025 17:35:54 +0100
Subject: [PATCH 62/62] Update docstring
Co-authored-by: paloma-martinez <104762252+paloma-martinez@users.noreply.github.com>
---
geos-pv/src/geos/pv/utils/workflowFunctions.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/geos-pv/src/geos/pv/utils/workflowFunctions.py b/geos-pv/src/geos/pv/utils/workflowFunctions.py
index 6fe08e709..28602fce0 100644
--- a/geos-pv/src/geos/pv/utils/workflowFunctions.py
+++ b/geos-pv/src/geos/pv/utils/workflowFunctions.py
@@ -26,8 +26,8 @@ def doExtractAndMerge(
outputCells (vtkMultiBlockDataSet): Output volume mesh
outputFaults (vtkMultiBlockDataSet): Output surface mesh
outputWells (vtkMultiBlockDataSet): Output well mesh
- extractFault (bool, Optional): True if SurfaceElementRegion needs to be extracted, False otherwise.
- extractWell (bool, Optional): True if WellElementRegion needs to be extracted, False otherwise.
+ extractFault (bool): True if SurfaceElementRegion needs to be extracted, False otherwise.
+ extractWell (bool): True if WellElementRegion needs to be extracted, False otherwise.
"""
# Extract blocks
blockExtractor: GeosBlockExtractor = GeosBlockExtractor( mesh,