diff --git a/docs/geos-posp.rst b/docs/geos-posp.rst index 19e5c205..efa3c25e 100644 --- a/docs/geos-posp.rst +++ b/docs/geos-posp.rst @@ -7,6 +7,4 @@ GEOS Post-Processing tools ./geos_posp_docs/home.rst - ./geos_posp_docs/modules.rst - - ./geos_posp_docs/visualization.rst + ./geos_posp_docs/modules.rst \ No newline at end of file diff --git a/docs/geos_posp_docs/PVplugins.rst b/docs/geos_posp_docs/PVplugins.rst deleted file mode 100644 index 66a3b583..00000000 --- a/docs/geos_posp_docs/PVplugins.rst +++ /dev/null @@ -1,14 +0,0 @@ -Paraview plugins -================ - -Paraview plugins were developed using Python 3.9.13 and are compatible with Paraview v5.12.0. - -The plugins include: - -* Visualization plugins to plot Mohr's circles and cross plots using Paraview Python View. - - -PVMohrCirclePlot plugin ---------------------------------- - -.. automodule:: PVplugins.PVMohrCirclePlot diff --git a/docs/geos_posp_docs/visu.PVUtils.rst b/docs/geos_posp_docs/visu.PVUtils.rst deleted file mode 100644 index 6603a216..00000000 --- a/docs/geos_posp_docs/visu.PVUtils.rst +++ /dev/null @@ -1,37 +0,0 @@ -PVUtils Package -=============== - -This packages consists of utilities for Paraview. - - -geos_posp.visu.PVUtils.DisplayOrganizationParaview module ------------------------------------------------------------- - -.. automodule:: geos_posp.visu.PVUtils.DisplayOrganizationParaview - :members: - :undoc-members: - :show-inheritance: - -geos_posp.visu.PVUtils.checkboxFunction module -------------------------------------------------- - -.. automodule:: geos_posp.visu.PVUtils.checkboxFunction - :members: - :undoc-members: - :show-inheritance: - -geos_posp.visu.PVUtils.paraviewTreatments module ---------------------------------------------------- - -.. automodule:: geos_posp.visu.PVUtils.paraviewTreatments - :members: - :undoc-members: - :show-inheritance: - -geos_posp.visu.PVUtils.matplotlibOptions module ----------------------------------------------------------- - -.. automodule:: geos_posp.visu.PVUtils.matplotlibOptions - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file diff --git a/docs/geos_posp_docs/visu.rst b/docs/geos_posp_docs/visu.rst deleted file mode 100644 index 66ec9bf8..00000000 --- a/docs/geos_posp_docs/visu.rst +++ /dev/null @@ -1,12 +0,0 @@ -Utilities -========= - -This package includes visualization tools dedicated to Paraview software. - - -.. toctree:: - :maxdepth: 4 - - visu.PVUtils - - visu.mohrCircles \ No newline at end of file diff --git a/docs/geos_posp_docs/visualization.rst b/docs/geos_posp_docs/visualization.rst deleted file mode 100644 index f63db3ec..00000000 --- a/docs/geos_posp_docs/visualization.rst +++ /dev/null @@ -1,9 +0,0 @@ -Visualization -============= - -.. toctree:: - :maxdepth: 4 - - PVplugins - - visu \ No newline at end of file diff --git a/docs/geos_posp_docs/visu.mohrCircles.rst b/docs/geos_pv_docs/mohrCircles.rst similarity index 62% rename from docs/geos_posp_docs/visu.mohrCircles.rst rename to docs/geos_pv_docs/mohrCircles.rst index 30e9a721..4a72b422 100644 --- a/docs/geos_posp_docs/visu.mohrCircles.rst +++ b/docs/geos_pv_docs/mohrCircles.rst @@ -3,18 +3,18 @@ Mohr's Circle Package This package includes utilities to compute and plot Mohr's Circles using the Python View from Paraview. -geos_posp.visu.mohrCircles.functionsMohrCircle module +geos.pv.utils.mohrCircles.functionsMohrCircle module -------------------------------------------------------- -.. automodule:: geos_posp.visu.mohrCircles.functionsMohrCircle +.. automodule:: geos.pv.utils.mohrCircles.functionsMohrCircle :members: :undoc-members: :show-inheritance: -geos_posp.visu.mohrCircles.plotMohrCircles module +geos.pv.utils.mohrCircles.plotMohrCircles module -------------------------------------------------- -.. automodule:: geos_posp.visu.mohrCircles.plotMohrCircles +.. automodule:: geos.pv.utils.mohrCircles.plotMohrCircles :members: :undoc-members: :show-inheritance: diff --git a/docs/geos_pv_docs/processing.rst b/docs/geos_pv_docs/processing.rst index 6b7c00ff..04a828d0 100644 --- a/docs/geos_pv_docs/processing.rst +++ b/docs/geos_pv_docs/processing.rst @@ -5,7 +5,7 @@ The plugins include: * A reader that parse GEOS output log file; * 3D mesh cleanning plugins; * Processing plugins to compute additional geomechanical properties; -* Visualization plugins to plot Mohr's circles (currently in `geos_posp`) and cross plots using Paraview Python View. +* Visualization plugins to plot Mohr's circles and cross plots using Paraview Python View. PVAttributeMapping @@ -67,3 +67,9 @@ PVGeomechanicsCalculator plugin --------------------------------------- .. automodule:: geos.pv.plugins.PVGeomechanicsCalculator + + +PVMohrCirclePlot plugin +--------------------------------- + +.. automodule:: geos.pv.plugins.PVMohrCirclePlot diff --git a/docs/geos_pv_docs/pyplotUtils.rst b/docs/geos_pv_docs/pyplotUtils.rst index 6b2b36c4..155eea78 100644 --- a/docs/geos_pv_docs/pyplotUtils.rst +++ b/docs/geos_pv_docs/pyplotUtils.rst @@ -10,4 +10,4 @@ geos.pv.pyplotUtils.matplotlibOptions module .. automodule:: geos.pv.pyplotUtils.matplotlibOptions :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: diff --git a/docs/geos_pv_docs/utilities.rst b/docs/geos_pv_docs/utilities.rst index 052d49e6..c3d83c45 100644 --- a/docs/geos_pv_docs/utilities.rst +++ b/docs/geos_pv_docs/utilities.rst @@ -11,3 +11,5 @@ Utilities pythonViewUtils utils + + mohrCircles diff --git a/geos-geomechanics/src/geos/geomechanics/model/MohrCircle.py b/geos-geomechanics/src/geos/geomechanics/model/MohrCircle.py index a917c3de..b0916da1 100644 --- a/geos-geomechanics/src/geos/geomechanics/model/MohrCircle.py +++ b/geos-geomechanics/src/geos/geomechanics/model/MohrCircle.py @@ -3,7 +3,7 @@ # SPDX-FileContributor: Alexandre Benedicto, Martin Lemay import numpy as np import numpy.typing as npt -from typing_extensions import Self +from typing_extensions import Self, Any from geos.geomechanics.processing.geomechanicsCalculatorFunctions import ( computeStressPrincipalComponentsFromStressVector, ) @@ -11,8 +11,7 @@ __doc__ = """ MohrCircle module define the Mohr's circle parameters. -Inputs are a 6 component stress vector, a circle id, and the mechanical -convention used for compression. +Inputs are a 6 component stress vector, and a circle id. The class computes principal components from stress vector during initialization. Accessors get access to these 3 principal components as well as circle center and radius. @@ -23,21 +22,21 @@ from processing.MohrCircle import MohrCircle - # create the object - stressVector :npt.NDArray[np.float64] - circleId :str - mohrCircle :MohrCircle = MohrCircle(circleId) + # Create the object + stressVector: npt.NDArray[np.float64] + circleId: str + mohrCircle: MohrCircle = MohrCircle(circleId) - # either directly set principal components (p3 <= p2 <= p1) + # Either directly set principal components (p3 <= p2 <= p1) mohrCircle.SetPrincipalComponents(p3, p2, p1) - # or compute them from stress vector + # Or compute them from stress vector mohrCircle.computePrincipalComponents(stressVector) - # access to members - id :str = mohrCircle.getCircleId() - p1, p2, p3 :float = mohrCircle.getPrincipalComponents() - radius :float = mohrCircle.getCircleRadius() - center :float = mohrCircle.getCircleCenter() + # Access to members + id: str = mohrCircle.getCircleId() + p1, p2, p3: float = mohrCircle.getPrincipalComponents() + radius: float = mohrCircle.getCircleRadius() + center: float = mohrCircle.getCircleCenter() """ @@ -49,27 +48,37 @@ def __init__( self: Self, circleId: str ) -> None: Args: circleId (str): Mohr's circle id. """ - self.m_circleId: str = circleId + self.circleId: str = circleId - self.m_p1: float = 0.0 - self.m_p2: float = 0.0 - self.m_p3: float = 0.0 + self.p1: float = 0.0 + self.p2: float = 0.0 + self.p3: float = 0.0 def __str__( self: Self ) -> str: """Overload of __str__ method.""" - return self.m_circleId + return self.circleId def __repr__( self: Self ) -> str: """Overload of __repr__ method.""" - return self.m_circleId + return self.circleId + + def __eq__( self: Self, other: Any ) -> bool: + """Overload of __eq__ method.""" + if not isinstance( other, MohrCircle ): + return NotImplemented + return self.circleId == other.circleId + + def __hash__( self: Self ) -> int: + """Overload of hash method.""" + return hash( self.circleId ) def setCircleId( self: Self, circleId: str ) -> None: """Set circle Id variable. Args: - circleId (str): circle Id. + circleId (str): Circle Id. """ - self.m_circleId = circleId + self.circleId = circleId def getCircleId( self: Self ) -> str: """Access the Id of the Mohr circle. @@ -77,32 +86,48 @@ def getCircleId( self: Self ) -> str: Returns: str: Id of the Mohr circle """ - return self.m_circleId + return self.circleId def getCircleRadius( self: Self ) -> float: - """Compute and return Mohr's circle radius from principal components.""" - return ( self.m_p1 - self.m_p3 ) / 2.0 + """Compute and return Mohr's circle radius from principal components. + + Returns: + float: Mohr circle radius. + """ + return ( self.p1 - self.p3 ) / 2.0 def getCircleCenter( self: Self ) -> float: - """Compute and return Mohr's circle center from principal components.""" - return ( self.m_p1 + self.m_p3 ) / 2.0 + """Compute and return Mohr's circle center from principal components. + + Returns: + float: Mohr circle center. + """ + return ( self.p1 + self.p3 ) / 2.0 def getPrincipalComponents( self: Self ) -> tuple[ float, float, float ]: - """Get Moh's circle principal components.""" - return ( self.m_p3, self.m_p2, self.m_p1 ) + """Get Moh's circle principal components. + + Returns: + tuple[float, float, float]: Mohr circle principal components. + """ + return ( self.p3, self.p2, self.p1 ) def setPrincipalComponents( self: Self, p3: float, p2: float, p1: float ) -> None: """Set principal components. Args: - p3 (float): first component. Must be the lowest. - p2 (float): second component. - p1 (float): third component. Must be the greatest. + p3 (float): First component. Must be the lowest. + p2 (float): Second component. + p1 (float): Third component. Must be the greatest. + + Raises: + ValueError: Expected p3 <= p2 <= p1. """ - assert ( p3 <= p2 ) and ( p2 <= p1 ), "Component order is wrong." - self.m_p3 = p3 - self.m_p2 = p2 - self.m_p1 = p1 + if not ( ( p3 <= p2 ) and ( p2 <= p1 ) ): + raise ValueError( "Component order is wrong. Expected p3 <= p2 <= p1." ) + self.p3 = p3 + self.p2 = p2 + self.p1 = p1 def computePrincipalComponents( self: Self, stressVector: npt.NDArray[ np.float64 ] ) -> None: """Calculate principal components. @@ -111,5 +136,5 @@ def computePrincipalComponents( self: Self, stressVector: npt.NDArray[ np.float6 stressVector (npt.NDArray[np.float64]): 6 components stress vector Stress vector must follow GEOS convention (XX, YY, ZZ, YZ, XZ, XY) """ - # get stress principal components - self.m_p3, self.m_p2, self.m_p1 = ( computeStressPrincipalComponentsFromStressVector( stressVector ) ) + # Get stress principal components + self.p3, self.p2, self.p1 = ( computeStressPrincipalComponentsFromStressVector( stressVector ) ) diff --git a/geos-posp/pyproject.toml b/geos-posp/pyproject.toml index ddd3d062..e1c05a89 100644 --- a/geos-posp/pyproject.toml +++ b/geos-posp/pyproject.toml @@ -7,7 +7,7 @@ include-package-data = true [tool.setuptools.packages.find] where = ["src"] -include = ["geos_posp*", "PVplugins*"] +include = ["geos_posp*"] exclude = ['tests*'] [project] @@ -90,5 +90,4 @@ source = ["geos-posp"] omit = [ "*/pyvistaUtils/*", "*/visu/*", - "*/PVplugins/*", ] \ No newline at end of file diff --git a/geos-posp/src/PVplugins/__init__.py b/geos-posp/src/PVplugins/__init__.py deleted file mode 100644 index 37e7c3cf..00000000 --- a/geos-posp/src/PVplugins/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -import sys - -# Add other packages path to sys path -dir_path = os.path.dirname( os.path.realpath( __file__ ) ) -python_root = '../../..' - -python_modules = ( 'geos-posp', 'geos-utils', 'geos-geomechanics', 'geos-mesh', 'geos-processing' ) - -for m in python_modules: - m_path = os.path.abspath( os.path.join( dir_path, python_root, m, 'src' ) ) - if m_path not in sys.path: - sys.path.insert( 0, m_path ) diff --git a/geos-posp/src/PVplugins/py.typed b/geos-posp/src/PVplugins/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/geos-posp/src/geos_posp/visu/PVUtils/DisplayOrganizationParaview.py b/geos-posp/src/geos_posp/visu/PVUtils/DisplayOrganizationParaview.py deleted file mode 100644 index 249ec4b8..00000000 --- a/geos-posp/src/geos_posp/visu/PVUtils/DisplayOrganizationParaview.py +++ /dev/null @@ -1,192 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. -# SPDX-FileContributor: Alexandre Benedicto -# ruff: noqa: E402 # disable Module level import not at top of file -from typing import Any - -from paraview.simple import ( # type: ignore[import-not-found] - AssignViewToLayout, CreateLayout, CreateView, Delete, GetLayoutByName, GetLayouts, GetViews, GetViewsInLayout, - RemoveLayout, SetActiveView, -) -from typing_extensions import Self - - -def buildNewLayoutWithPythonView() -> Any: # noqa: ANN401 - """Create a new PythonView layout.""" - # create a new layout - organization: DisplayOrganizationParaview = DisplayOrganizationParaview() - layout_names: list[ str ] = organization.getLayoutsNames() - nb_layouts: int = len( layout_names ) - layoutName: str = "Layout #" + str( nb_layouts + 1 ) - # increment layout index until the layout name is a new one - cpt: int = 1 - while layoutName in layout_names: - layoutName = "Layout #" + str( nb_layouts + cpt ) - cpt += 1 - organization.addLayout( layoutName ) - - # add a new python view to the layout - organization.addViewToLayout( "PythonView", layoutName, 0 ) - return organization.getLayoutViews()[ layoutName ][ 0 ] - - -class DisplayOrganizationParaview: - """Object to manage Paraview layouts.""" - - def __init__( self: Self ) -> None: - """Keeps track of Paraview layouts and views when created or removed.""" - self._layouts_keys: list[ Any ] = [] - self._layout_names: list[ str ] = [] - self._views_cpt: int = 0 - self._layout_views: dict[ str, Any ] = {} - self._views_name: dict[ str, Any ] = {} - self.initLayouts() - self.initLayoutViews() - - def initLayouts( self: Self ) -> None: - """Initialize layouts.""" - self._layouts_keys = list( GetLayouts().keys() ) - self._layouts_names = [] - for layout_tuple in self._layouts_keys: - self._layouts_names.append( layout_tuple[ 0 ] ) - - def getLayoutsKeys( self: Self ) -> list[ Any ]: - """Get layout keys. - - Returns: - list[Any]: list of layout keys. - """ - return self._layouts_keys - - def getLayoutsNames( self: Self ) -> list[ str ]: - """Get layout names. - - Returns: - list[str]: list of layout names. - """ - return self._layouts_names - - def getNumberLayouts( self: Self ) -> int: - """Get the number of layouts. - - Returns: - int: number of layouts. - """ - return len( self._layouts_keys ) - - def getViewsCpt( self: Self ) -> int: - """Get the number of views. - - Returns: - int: number of views. - """ - return self._views_cpt - - def addOneToCpt( self: Self ) -> None: - """Increment number of views.""" - self._views_cpt += 1 - - def initLayoutViews( self: Self ) -> None: - """Initialize layout views.""" - self._views_name = {} - self._layout_views = {} - all_views: list[ Any ] = GetViews() - layouts_keys: list[ Any ] = self.getLayoutsKeys() - layout_names: list[ str ] = self.getLayoutsNames() - for i in range( self.getNumberLayouts() ): - self._layout_views[ layout_names[ i ] ] = [] - views_in_layout = GetViewsInLayout( GetLayouts()[ layouts_keys[ i ] ] ) - for view in all_views: - if view in views_in_layout: - self._layout_views[ layout_names[ i ] ].append( view ) - name_view: str = "view" + str( self.getViewsCpt() ) - self._views_name[ name_view ] = view - self.addOneToCpt() - - def getLayoutViews( self: Self ) -> dict[ str, Any ]: - """Get layout views. - - Returns: - dict[Any:Any]: dictionnary of layout views. - """ - return self._layout_views - - def getViewsName( self: Self ) -> dict[ str, Any ]: - """Get view names. - - Returns: - list[str]: list of view names. - """ - return self._views_name - - def updateOrganization( self: Self ) -> None: - """Update layouts.""" - self._views_cpt = 0 - self.initLayouts() - self.initLayoutViews() - - def addLayout( self: Self, new_layout_name: str ) -> None: - """Add a layout. - - Args: - new_layout_name (str): name of the new layout. - """ - if new_layout_name not in self.getLayoutsNames(): - CreateLayout( new_layout_name ) - else: - print( f'This layout name "{new_layout_name}" is already used, please pick a new one.\n' ) - self.updateOrganization() - - def removeLayout( self: Self, layout_name: str ) -> None: - """Remove a layout. - - Args: - layout_name (str): name of the layout to remove. - """ - if layout_name not in self.getLayoutsNames(): - RemoveLayout( GetLayoutByName( layout_name ) ) - else: - print( f'This layout name "{layout_name}" does not exist.' ) - self.updateOrganization() - - def addViewToLayout( self: Self, viewType: str, layout_name: str, position: int ) -> None: - """Add a view to a layout. - - Args: - viewType (str): type of view. - layout_name (str): name of the layout. - position (int): position of the view. - """ - SetActiveView( None ) - layout_to_use = GetLayoutByName( layout_name ) - new_view = CreateView( viewType ) - AssignViewToLayout( view=new_view, layout=layout_to_use, hint=position ) - self.updateOrganization() - - def RemoveViewFromLayout( self: Self, view_name: str, layout_name: str, position: int ) -> None: - """Remove a view from a layout. - - Args: - view_name (str): name of view. - layout_name (str): name of the layout. - position (int): position of the view. - """ - views_name: dict[ str, Any ] = self.getViewsName() - view_to_delete = views_name[ view_name ] - SetActiveView( view_to_delete ) - Delete( view_to_delete ) - del view_to_delete - layout_to_use = GetLayoutByName( layout_name ) - layout_to_use.Collapse( position ) - self.updateOrganization() - - def SwapCellsInLayout( self: Self, layout_name: str, position1: int, position2: int ) -> None: - """Swap views in a layout. - - Args: - layout_name (str): name of the layout. - position1 (int): first position of the view. - position2 (int): second position of the view. - """ - layout_to_use = GetLayoutByName( layout_name ) - layout_to_use.SwapCells( position1, position2 ) diff --git a/geos-posp/src/geos_posp/visu/PVUtils/checkboxFunction.py b/geos-posp/src/geos_posp/visu/PVUtils/checkboxFunction.py deleted file mode 100644 index 6e0a250e..00000000 --- a/geos-posp/src/geos_posp/visu/PVUtils/checkboxFunction.py +++ /dev/null @@ -1,22 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. -# SPDX-FileContributor: Alexandre Benedicto -# ruff: noqa -# type: ignore -def createModifiedCallback( anobject ): - """Helper for the creation and use of vtkDataArraySelection in ParaView. - - Args: - anobject: any object. - """ - import weakref - - weakref_obj = weakref.ref( anobject ) - anobject = None - - def _markmodified( *args, **kwars ): - o = weakref_obj() - if o is not None: - o.Modified() - - return _markmodified diff --git a/geos-posp/src/geos_posp/visu/PVUtils/matplotlibOptions.py b/geos-posp/src/geos_posp/visu/PVUtils/matplotlibOptions.py deleted file mode 100644 index 311678ce..00000000 --- a/geos-posp/src/geos_posp/visu/PVUtils/matplotlibOptions.py +++ /dev/null @@ -1,88 +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 - -from enum import Enum - -from typing_extensions import Self - - -class OptionSelectionEnum( Enum ): - - def __init__( self: Self, displayName: str, optionValue: str ) -> None: - """Define the enumeration to options for Paraview selectors. - - Args: - displayName (str): name displayed in the selector - optionValue (str): value used by matplotlib. - - Defaults to None (same optionName as displayName) - """ - self.displayName: str = displayName - self.optionValue: str = optionValue - - -class LegendLocationEnum( OptionSelectionEnum ): - BEST = ( "best", "best" ) - UPPER_LEFT = ( "upper left", "upper left" ) - UPPER_CENTER = ( "upper center", "upper center" ) - UPPER_RIGHT = ( "upper right", "upper right" ) - CENTER_LEFT = ( "center left", "center left" ) - CENTER = ( "center", "center" ) - CENTER_RIGHT = ( "center right", "center right" ) - LOWER_LEFT = ( "lower left", "lower left" ) - LOWER_CENTER = ( "lower center", "lower center" ) - LOWER_RIGHT = ( "lower right", "lower right" ) - - -class FontStyleEnum( OptionSelectionEnum ): - NORMAL = ( "normal", "normal" ) - ITALIC = ( "italic", "italic" ) - OBLIQUE = ( "oblique", "oblique" ) - - -class FontWeightEnum( OptionSelectionEnum ): - NORMAL = ( "normal", "normal" ) - BOLD = ( "bold", "bold" ) - HEAVY = ( "heavy", "heavy" ) - LIGHT = ( "light", "light" ) - - -class LineStyleEnum( OptionSelectionEnum ): - NONE = ( "None", "None" ) - SOLID = ( "solid", "-" ) - DASHED = ( "dashed", "--" ) - DASHDOT = ( "dashdot", "-." ) - DOTTED = ( "dotted", ":" ) - - -class MarkerStyleEnum( OptionSelectionEnum ): - NONE = ( "None", "" ) - POINT = ( "point", "." ) - CIRCLE = ( "circle", "o" ) - TRIANGLE = ( "triangle", "^" ) - SQUARE = ( "square", "s" ) - STAR = ( "star", "*" ) - DIAMOND = ( "diamond", "D" ) - PLUS = ( "plus", "+" ) - X = ( "x", "x" ) - - -def optionEnumToXml( enumObj: OptionSelectionEnum ) -> str: - """Creates an enumeration domain from an OptionSelectionEnum object. - - Dedicated to the dropdown widgets of paraview plugin. - - Args: - enumObj (OptionSelectionEnum): Enumeration values to put in the dropdown - widget. - - Returns: - str: the XML string. - """ - xml: str = """""" - for i, unitObj in enumerate( list( enumObj ) ): # type: ignore[call-overload] - xml += f"""""" - xml += """""" - return xml diff --git a/geos-posp/src/geos_posp/visu/PVUtils/paraviewTreatments.py b/geos-posp/src/geos_posp/visu/PVUtils/paraviewTreatments.py deleted file mode 100644 index 239df5f8..00000000 --- a/geos-posp/src/geos_posp/visu/PVUtils/paraviewTreatments.py +++ /dev/null @@ -1,588 +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 -from enum import Enum -from typing import Any, Union - -import numpy as np -import numpy.typing as npt -import pandas as pd # type: ignore[import-untyped] -from geos.utils.GeosOutputsConstants import ( - ComponentNameEnum, - GeosMeshOutputsEnum, -) -from packaging.version import Version - -# TODO: remove this condition when all codes are adapted for Paraview 6.0 -import vtk -if Version( vtk.__version__ ) >= Version( "9.5" ): - from vtkmodules.vtkFiltersParallel import vtkMergeBlocks -else: - from paraview.modules.vtkPVVTKExtensionsMisc import ( # type: ignore[import-not-found] - vtkMergeBlocks, ) - -from paraview.simple import ( # type: ignore[import-not-found] - FindSource, GetActiveView, GetAnimationScene, GetDisplayProperties, GetSources, servermanager, -) -from vtkmodules.vtkCommonCore import ( - vtkDataArray, - vtkDataArraySelection, - vtkDoubleArray, - vtkPoints, -) -from vtkmodules.vtkCommonDataModel import ( - vtkCompositeDataSet, - vtkDataObject, - vtkMultiBlockDataSet, - vtkPolyData, - vtkTable, - vtkUnstructuredGrid, -) - -from geos.mesh.utils.arrayHelpers import ( - getArrayInObject, - isAttributeInObject, -) - -# valid sources for Python view configurator -# TODO: need to be consolidated -HARD_CODED_VALID_PVC_TYPE: set[ str ] = { "GeosLogReader", "RenameArrays" } - - -def vtkTableToDataframe( table: vtkTable ) -> pd.DataFrame: - """From a vtkTable, creates and returns a pandas dataframe. - - Args: - table (vtkTable): vtkTable object. - - Returns: - pd.DataFrame: Pandas dataframe. - """ - data: list[ dict[ str, Any ] ] = [] - for rowIndex in range( table.GetNumberOfRows() ): - rowData: dict[ str, Any ] = {} - for colIndex in range( table.GetNumberOfColumns() ): - colName: str = table.GetColumnName( colIndex ) - cellValue: Any = table.GetValue( rowIndex, colIndex ) - # we have a vtkVariant value, we need a float - cellValueF: float = cellValue.ToFloat() - rowData[ colName ] = cellValueF - data.append( rowData ) - df: pd.DataFrame = pd.DataFrame( data ) - return df - - -def vtkPolyDataToPointsDataframe( polydata: vtkPolyData ) -> pd.DataFrame: - """Creates a pandas dataframe containing points data from vtkPolyData. - - Args: - polydata (vtkPolyData): vtkPolyData object. - - Returns: - pd.DataFrame: Pandas dataframe containing the points data. - """ - points: vtkPoints = polydata.GetPoints() - assert points is not None, "Points is undefined." - nbrPoints: int = points.GetNumberOfPoints() - data: dict[ str, Any ] = { - "Point ID": np.empty( nbrPoints ), - "PointsX": np.empty( nbrPoints ), - "PointsY": np.empty( nbrPoints ), - "PointsZ": np.empty( nbrPoints ), - } - for pointID in range( nbrPoints ): - point: tuple[ float, float, float ] = points.GetPoint( pointID ) - data[ "Point ID" ][ pointID ] = pointID - data[ "PointsX" ][ pointID ] = point[ 0 ] - data[ "PointsY" ][ pointID ] = point[ 1 ] - data[ "PointsZ" ][ pointID ] = point[ 2 ] - pointData = polydata.GetPointData() - nbrArrays: int = pointData.GetNumberOfArrays() - for i in range( nbrArrays ): - arrayToUse = pointData.GetArray( i ) - arrayName: str = pointData.GetArrayName( i ) - subArrayNames: list[ str ] = findSubArrayNames( arrayToUse, arrayName ) - # Collect the data for each sub array - for ind, name in enumerate( subArrayNames ): - data[ name ] = np.empty( nbrPoints ) - for k in range( nbrPoints ): - # Every element of the tuple correspond to one distinct - # sub array so we only need one value at a time - value: float = arrayToUse.GetTuple( k )[ ind ] - data[ name ][ k ] = value - df: pd.DataFrame = pd.DataFrame( data ).set_index( "Point ID" ) - return df - - -def vtkUnstructuredGridCellsToDataframe( grid: vtkUnstructuredGrid ) -> pd.DataFrame: - """Creates a pandas dataframe containing points data from vtkUnstructuredGrid. - - Args: - grid (vtkUnstructuredGrid): vtkUnstructuredGrid object. - - Returns: - pd.DataFrame: Pandas dataframe. - """ - cellIdAttributeName = GeosMeshOutputsEnum.VTK_ORIGINAL_CELL_ID.attributeName - cellData = grid.GetCellData() - numberCells: int = grid.GetNumberOfCells() - data: dict[ str, Any ] = {} - for i in range( cellData.GetNumberOfArrays() ): - arrayToUse = cellData.GetArray( i ) - arrayName: str = cellData.GetArrayName( i ) - subArrayNames: list[ str ] = findSubArrayNames( arrayToUse, arrayName ) - # Collect the data for each sub array - for ind, name in enumerate( subArrayNames ): - data[ name ] = np.empty( numberCells ) - for k in range( numberCells ): - # Every element of the tuple correspond to one distinct - # sub array so we only need one value at a time - value: float = arrayToUse.GetTuple( k )[ ind ] - data[ name ][ k ] = value - df: pd.DataFrame = pd.DataFrame( data ).astype( { cellIdAttributeName: int } ) - - # set cell ids as index - - # df = df.astype({cellIdAttributeName: int}) - return df.set_index( cellIdAttributeName ) - - -def vtkToDataframe( dataset: vtkDataObject ) -> pd.DataFrame: - """Creates a dataframe containing points data from vtkTable or vtkPolyData. - - Args: - dataset (Any): dataset to convert if possible. - - Returns: - pd.DataFrame: if the dataset is in the right format. - """ - if isinstance( dataset, vtkTable ): - return vtkTableToDataframe( dataset ) - elif isinstance( dataset, vtkPolyData ): - return vtkPolyDataToPointsDataframe( dataset ) - elif isinstance( dataset, vtkUnstructuredGrid ): - return vtkUnstructuredGridCellsToDataframe( dataset ) - else: - raise AssertionError( f"Invalid dataset format {type(dataset)}. " + - "Supported formats are: vtkTable, vtkpolyData and vtkUnstructuredGrid" ) - - -def findSubArrayNames( vtkArray: vtkDataArray, arrayName: str ) -> list[ str ]: - """Get sub array names from multi array attributes. - - Because arrays in ParaView can be of multiple dimensions, - it can be difficult to convert these arrays to numpy arrays. - Therefore, we can split the original array into multiple sub - one dimensional arrays. In that case, new sub names need to be - derived from the original array to be used. - - Args: - vtkArray (vtkDataArray): Array from vtk library. - arrayName (str): Name of the array. - - Returns: - list[str]: Sub array names from original array name. - """ - # The ordering of six elements can seem odd but is adapted to - # Geos output format of stress as : - # sigma11, sigma22, sigma33, sigma23, sigma13, sigma12 - sixComponents: tuple[ str, str, str, str, str, str ] = ComponentNameEnum.XYZ.value - nbrComponents: int = vtkArray.GetNumberOfComponents() - subArrayNames: list[ str ] = [] - if nbrComponents == 1: - subArrayNames.append( arrayName ) - elif nbrComponents < 6: - for j in range( nbrComponents ): - subArrayNames.append( arrayName + "_" + sixComponents[ j ] ) - else: - for j in range( nbrComponents ): - subArrayNames.append( arrayName + "_" + str( j ) ) - return subArrayNames - - -def getDataframesFromMultipleVTKSources( sourceNames: set[ str ], commonColumn: str ) -> list[ pd.DataFrame ]: - """Creates the dataframe from each source if they have the commonColumn. - - Args: - sourceNames (set[str]): list of sources. - commonColumn (str): common column name. - - Returns: - list[pd.DataFrame]: output dataframe. - """ - # indexSource: int = commonColumn.rfind("__") - # commonColumnNoSource: str = commonColumn[:indexSource] - validDataframes: list[ pd.DataFrame ] = [] - for name in sourceNames: - source = FindSource( name ) - assert source is not None, "Source is undefined." - dataset = servermanager.Fetch( source ) - assert dataset is not None, "Dataset is undefined." - currentDF: pd.DataFrame = vtkToDataframe( dataset ) - if commonColumn in currentDF.columns: - dfModified = currentDF.rename( - columns={ col: col + "__" + name - for col in currentDF.columns if col != commonColumn } ) - validDataframes.append( dfModified ) - else: - print( f"The source <<{name}>> could not be used" + " to plot because the variable named <<" + - f"{commonColumn}>> could not be found." ) - return validDataframes - - -def mergeDataframes( dataframes: list[ pd.DataFrame ], commonColumn: str ) -> pd.DataFrame: - """Merge all dataframes into a single one by using the common column. - - Args: - dataframes (list[pd.DataFrame]): List of dataframes from - getDataframesFromMultipleVTKSources. - commonColumn (str): Name of the only common column between - all of the dataframes. - - Returns: - pd.DataFrame: Merged dataframes into a single one by 'outer' - on the commonColumn. - """ - assert len( dataframes ) > 0 - if len( dataframes ) == 1: - return dataframes[ 0 ] - else: - df0: pd.DataFrame = dataframes[ 0 ] - df1: pd.DataFrame = dataframes[ 1 ] - merged: pd.DataFrame = df0.merge( df1, on=commonColumn, how="outer" ) - if len( dataframes ) > 2: - for df in dataframes[ 2: ]: - merged = merged.merge( df, on=commonColumn, how="outer" ) - return merged - - -def addDataframeColumnsToVtkPolyData( polyData: vtkPolyData, df: pd.DataFrame ) -> vtkPolyData: - """Add columns from a dataframe to a vtkPolyData. - - Args: - polyData (vtkPolyData): vtkPolyData before modifcation. - df (pd.DataFrame): Pandas dataframe. - - Returns: - vtkPolyData: vtkPolyData with new arrays. - """ - for column_name in df.columns: - column = df[ column_name ].values - array = vtkDoubleArray() - array.SetName( column_name ) - array.SetNumberOfValues( polyData.GetNumberOfPoints() ) - for i in range( polyData.GetNumberOfPoints() ): - array.SetValue( i, column[ i ] ) - polyData.GetPointData().AddArray( array ) - - # Update vtkPolyData object - polyData.GetPointData().Modified() - polyData.Modified() - return polyData - - -# Functions to help the processing of PythonViewConfigurator - - -def getPossibleSourceNames() -> set[ str ]: - """Get the list of valid source names for PythonViewConfigurator. - - In PythonViewConfigurator, multiple sources can be considered as - valid inputs. We want the user to know the names of every of these - sources that can be used to plot data. This function therefore identifies - which source names are valid to be used later as sources. - - Returns: - set[str]: Source names in the paraview pipeline. - """ - # get all sources different from PythonViewConfigurator - validNames: set[ str ] = set() - for k in GetSources(): - sourceName: str = k[ 0 ] - source = FindSource( sourceName ) - if ( source is not None ) and ( "PythonViewConfigurator" not in source.__str__() ): - dataset = servermanager.Fetch( source ) - if dataset.IsA( "vtkPolyData" ) or dataset.IsA( "vtkTable" ): - validNames.add( sourceName ) - return validNames - - -def usefulSourceNamesPipeline() -> set[ str ]: - """Get the list of valid pipelines for PythonViewConfigurator. - - When using the PythonViewConfigurator, we want to check if the sources - in the ParaView pipeline are compatible with what the filter can take as - input. So this function scans every sources of the pipeline and if it - corresponds to one of the hardcoded valid types, we keep the name. - They are right now : ["GeosLogReader", "RenameArrays"] - - Returns: - set[str]: [sourceName1, ..., sourceNameN] - """ - usefulSourceNames: set[ str ] = set() - allSourceNames: set[ str ] = { n[ 0 ] for n, s in GetSources().items() } - for name in allSourceNames: - source = FindSource( name ) - if type( source ).__name__ in HARD_CODED_VALID_PVC_TYPE: - usefulSourceNames.add( name ) - return usefulSourceNames - - -def getDatasFromSources( sourceNames: set[ str ] ) -> dict[ str, pd.DataFrame ]: - """Get the data from input sources. - - Args: - sourceNames (set[str]): [sourceName1, ..., sourceNameN] - - Returns: - dict[[str, pd.DataFrame]]: dictionary where source names are keys and - dataframe are values. - { sourceName1: servermanager.Fetch(FindSource(sourceName1)), - ... - sourceNameN: servermanager.Fetch(FindSource(sourceNameN)) } - """ - usefulDatas: dict[ str, Any ] = {} - for name in sourceNames: - dataset = servermanager.Fetch( FindSource( name ) ) - usefulDatas[ name ] = dataset - return usefulDatas - - -def usefulVisibleDatasPipeline() -> dict[ str, Any ]: - """Get the list of visible pipelines. - - When using the PythonViewConfigurator, we want to collect the data of - each source that is visible in the paraview pipeline and that is - compatible as input data for the filter. Therefore, only certain types of - sources will be considered as valid. They are right now : - ["GeosLogReader", "RenameArrays"] - - Finally, if the sources are visible and valid, we access their data and - return the names of the source and their respective data. - - Returns: - dict[str, 'vtkInformation']: dictionary of source names and data from - pipeline. - { sourceName1: servermanager.Fetch(FindSource(sourceName1)), - ... - sourceNameN: servermanager.Fetch(FindSource(sourceNameN)) } - """ - usefulDatas: dict[ str, Any ] = {} - sourceNamesVisible: set[ str ] = set() - for n, s in GetSources().items(): - if servermanager.GetRepresentation( s, GetActiveView() ) is not None: - displayProperties = GetDisplayProperties( s, view=GetActiveView() ) - if ( displayProperties is not None ) and ( displayProperties.Visibility == 1 ): - sourceNamesVisible.add( n[ 0 ] ) - - for name in sourceNamesVisible: - source = FindSource( name ) - if type( source ).__name__ in HARD_CODED_VALID_PVC_TYPE: - usefulDatas[ name ] = servermanager.Fetch( FindSource( name ) ) - return usefulDatas - - -def isFilter( sourceName: str ) -> bool: - """Identify if a source name can link to a filter in the ParaView pipeline. - - Args: - sourceName (str): name of a source object in the pipeline - - Returns: - bool: True if filter, False instead. - """ - source: Any = FindSource( sourceName ) - if source is None: - print( f"sourceName <<{sourceName}>> does not exist in the pipeline" ) - return False - else: - try: - test: Any = source.GetClientSideObject().GetInputAlgorithm() # noqa: F841 - return True - except Exception: - return False - - -def getFilterInput( sourceName: str ) -> vtkDataObject: - """Access the vtk dataset that is used as input for a filter. - - Args: - sourceName (str): name of a source object in the pipeline. - - Returns: - Any: The vtk dataset that serves as input for the filter. - """ - filtre = FindSource( sourceName ) - assert filtre is not None, "Source is undefined." - clientSideObject = filtre.GetClientSideObject() - assert clientSideObject is not None, "Client Side Object is undefined." - inputAlgo = clientSideObject.GetInputAlgorithm() - assert inputAlgo is not None, "Input Algorithm is undefined." - inputValues = inputAlgo.GetInput() - if isinstance( inputValues, vtkDataObject ): - return inputValues - return vtkDataObject() - - -def getArrayChoices( array: vtkDataArraySelection ) -> list[ str ]: - """Extracts the column names of input array when they are enabled. - - Args: - array (vtkDataArraySelection): input data - - Returns: - set[str]: [columnName1, ..., columnNameN] - """ - checkedColumns: list[ str ] = [] - for i in range( array.GetNumberOfArrays() ): - columnName: str = array.GetArrayName( i ) - if array.ArrayIsEnabled( columnName ): - checkedColumns.append( columnName ) - return checkedColumns - - -def integrateSourceNames( sourceNames: set[ str ], arrayChoices: set[ str ] ) -> set[ str ]: - """Aggregate source and arrayChoices names. - - When creating the user choices in PythonViewConfigurator, you need - to take into account both the source names and the choices of curves - to have user choices corresponding to the column names of the dataframe - with the data to be plot. - - Args: - sourceNames (set[str]): Name of sources found in ParaView pipeline. - arrayChoices (set[str]): Column names of the vtkdataarrayselection. - - Returns: - set[str]: [sourceName1__choice1, sourceName1__choice2, - ..., sourceNameN__choiceN] - """ - completeNames: set[ str ] = set() - for sourceName in sourceNames: - for choice in arrayChoices: - completeName: str = choice + "__" + sourceName - completeNames.add( completeName ) - return completeNames - - -def getVtkOriginalCellIds( mesh: Union[ vtkMultiBlockDataSet, vtkCompositeDataSet, vtkDataObject ] ) -> list[ str ]: - """Get vtkOriginalCellIds from a vtkUnstructuredGrid object. - - Args: - mesh (vtkMultiBlockDataSet|vtkCompositeDataSet|vtkDataObject): input mesh. - - Returns: - list[str]: ids of the cells. - """ - # merge blocks for vtkCompositeDataSet - mesh2: vtkUnstructuredGrid = mergeFilterPV( mesh ) - name: str = GeosMeshOutputsEnum.VTK_ORIGINAL_CELL_ID.attributeName - assert isAttributeInObject( mesh2, name, False ), f"Attribute {name} is not in the mesh." - return [ str( int( ide ) ) for ide in getArrayInObject( mesh2, name, False ) ] - - -def strEnumToEnumerationDomainXml( enumObj: Enum ) -> str: - """Creates an enumeration domain from an Enum objec. - - Creates an enumeration domain from an Enum objec - for the dropdown widgets of paraview plugin. - - Args: - enumObj (Enum): Enumeration values to put in the dropdown widget. - - Returns: - str: the XML string. - """ - xml: str = """""" - for i, unitObj in enumerate( list( enumObj ) ): # type: ignore[call-overload] - xml += f"""""" - xml += """""" - return xml - - -def strListToEnumerationDomainXml( properties: Union[ list[ str ], set[ str ] ] ) -> str: - """Creates an enumeration domain from a list of strings. - - Creates an enumeration domain from a list of strings - for the dropdown widgets of paraview plugin. - - Args: - properties (set[str] | list[str]): Properties to put in the dropdown widget. - - Returns: - str: the XML string. - """ - xml: str = """""" - for i, prop in enumerate( list( properties ) ): - xml += f"""""" - xml += """""" - return xml - - -def dataframeForEachTimestep( sourceName: str ) -> dict[ str, pd.DataFrame ]: - """Get the data from source at each time step. - - In ParaView, a source object can contain data for multiple - timesteps. If so, knowing the source name, we can access its data - for each timestep and store it in a dict where the keys are the - timesteps and the values the data at each one of them. - - Args: - sourceName (str): Name of the source in ParaView pipeline. - - Returns: - dict[str, pd.DataFrame]: dictionary where time is the key and dataframe - is the value. - """ - animationScene = GetAnimationScene() - assert animationScene is not None, "animationScene is undefined." - # we set the animation to the initial timestep - animationScene.GoToFirst() - source = FindSource( sourceName ) - dataset: vtkDataObject = servermanager.Fetch( source ) - assert dataset is not None, "Dataset is undefined." - dataset2: vtkUnstructuredGrid = mergeFilterPV( dataset ) - time: str = str( animationScene.TimeKeeper.Time ) - dfPerTimestep: dict[ str, pd.DataFrame ] = { time: vtkToDataframe( dataset2 ) } - # then we iterate on the other timesteps of the source - for _ in range( animationScene.NumberOfFrames ): # type: ignore - animationScene.GoToNext() - source = FindSource( sourceName ) - dataset = servermanager.Fetch( source ) - dataset2 = mergeFilterPV( dataset ) - time = str( animationScene.TimeKeeper.Time ) - dfPerTimestep[ time ] = vtkToDataframe( dataset2 ) - return dfPerTimestep - - -def getTimeStepIndex( time: float, timeSteps: npt.NDArray[ np.float64 ] ) -> int: - """Get the time step index of input time from the list of time steps. - - Args: - time (float): time - timeSteps (npt.NDArray[np.float64]): Array of time steps - - Returns: - int: time step index - """ - indexes: npt.NDArray[ np.int64 ] = np.where( np.isclose( timeSteps, time ) )[ 0 ] - assert ( indexes.size > 0 ), f"Current time {time} does not exist in the selected object." - return int( indexes[ 0 ] ) - - -def mergeFilterPV( input: vtkDataObject, ) -> vtkUnstructuredGrid: - """Apply Paraview merge block filter. - - Args: - input (vtkMultiBlockDataSet | vtkCompositeDataSet | vtkDataObject): composite - object to merge blocks - - Returns: - vtkUnstructuredGrid: merged block object - - """ - mergeFilter: vtkMergeBlocks = vtkMergeBlocks() - mergeFilter.SetInputData( input ) - mergeFilter.Update() - return mergeFilter.GetOutputDataObject( 0 ) diff --git a/geos-posp/src/geos_posp/visu/__init__.py b/geos-posp/src/geos_posp/visu/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/geos-posp/src/geos_posp/visu/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/geos-posp/src/geos_posp/visu/mohrCircles/__init__.py b/geos-posp/src/geos_posp/visu/mohrCircles/__init__.py deleted file mode 100644 index 2aa543df..00000000 --- a/geos-posp/src/geos_posp/visu/mohrCircles/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -MOHR_CIRCLE_PATH: str = "geos_posp/visu/mohrCircles/" -MOHR_CIRCLE_ANALYSIS_MAIN = "mainMohrCircles.py" diff --git a/geos-pv/examples/PVproxyWidgetsDynamic.py b/geos-pv/examples/PVproxyWidgetsDynamic.py index 257d05dd..b0e6d81b 100644 --- a/geos-pv/examples/PVproxyWidgetsDynamic.py +++ b/geos-pv/examples/PVproxyWidgetsDynamic.py @@ -16,7 +16,7 @@ from geos.pv.utils.paraviewTreatments import ( strListToEnumerationDomainXml, strEnumToEnumerationDomainXml, getArrayChoices ) -from geos_posp.visu.PVUtils.checkboxFunction import ( # type: ignore[attr-defined] +from geos.pv.utils.checkboxFunction import ( # type: ignore[attr-defined] createModifiedCallback, ) from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase diff --git a/geos-posp/src/PVplugins/PVMohrCirclePlot.py b/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py similarity index 62% rename from geos-posp/src/PVplugins/PVMohrCirclePlot.py rename to geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py index 5a04106c..d6efdfd5 100644 --- a/geos-posp/src/PVplugins/PVMohrCirclePlot.py +++ b/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py @@ -1,9 +1,10 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. -# SPDX-FileContributor: Alexandre Benedicto, Martin Lemay +# SPDX-FileContributor: Alexandre Benedicto, Martin Lemay, Paloma Martinez # ruff: noqa: E402 # disable Module level import not at top of file -import os import sys +import logging +from pathlib import Path from enum import Enum from typing import Any, Union, cast @@ -14,42 +15,43 @@ 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, ) + from typing_extensions import Self from vtkmodules.vtkCommonCore import vtkDataArraySelection as vtkDAS from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector from vtkmodules.vtkCommonDataModel import ( - vtkMultiBlockDataSet, - vtkUnstructuredGrid, -) + vtkUnstructuredGrid, ) -dir_path = os.path.dirname( os.path.realpath( __file__ ) ) -parent_dir_path = os.path.dirname( dir_path ) -if parent_dir_path not in sys.path: - sys.path.append( parent_dir_path ) +# Update sys.path to load all GEOS Python Package dependencies +geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent +sys.path.insert( 0, str( geos_pv_path / "src" ) ) +from geos.pv.utils.config import update_paths -import PVplugins # noqa: F401 +update_paths() -import geos_posp.visu.mohrCircles.functionsMohrCircle as mcf -import geos_posp.visu.PVUtils.paraviewTreatments as pvt from geos.geomechanics.model.MohrCircle import MohrCircle from geos.utils.enumUnits import Pressure, enumerationDomainUnit from geos.utils.GeosOutputsConstants import ( FAILURE_ENVELOPE, GeosMeshOutputsEnum, ) -from geos.utils.Logger import Logger, getLogger +from geos.utils.Logger import CustomLoggerFormatter from geos.utils.PhysicalConstants import ( DEFAULT_FRICTION_ANGLE_DEG, DEFAULT_FRICTION_ANGLE_RAD, DEFAULT_ROCK_COHESION, ) from geos.mesh.utils.arrayHelpers import getArrayInObject -from geos.mesh.utils.multiblockModifiers import mergeBlocks -from geos_posp.visu.PVUtils.checkboxFunction import ( # type: ignore[attr-defined] + +import geos.pv.utils.mohrCircles.functionsMohrCircle as mcf +import geos.pv.utils.paraviewTreatments as pvt +from geos.pv.utils.checkboxFunction import ( # type: ignore[attr-defined] createModifiedCallback, ) -from geos_posp.visu.PVUtils.DisplayOrganizationParaview import ( +from geos.pv.utils.DisplayOrganizationParaview import ( buildNewLayoutWithPythonView, ) -from geos_posp.visu.PVUtils.matplotlibOptions import ( +from geos.pv.pyplotUtils.matplotlibOptions import ( FontStyleEnum, FontWeightEnum, LegendLocationEnum, @@ -58,9 +60,10 @@ OptionSelectionEnum, optionEnumToXml, ) +from geos.pv.utils.mohrCircles.functionsMohrCircle import StressConventionEnum __doc__ = """ -PVMohrCirclePlot is a Paraview plugin that allows to compute and plot +PVMohrCirclePlot is a ParaView plugin that allows to compute and plot Mohr's circles of selected cells and times from effective stress attribute. Input is a vtkMultiBlockDataSet or vtkUnstructuredGrid. @@ -70,15 +73,32 @@ To use it: -* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVMohrCirclePlot. -* Select the mesh containing the cells you want to plot Mohr's circles. -* Search and Apply Mohr's Circle Plot Filter. +This plugin requires the presence of a `stressEffective` attribute in the mesh. Moreover, several timesteps should also be detected. + +.. Warning:: + The whole ParaView pipeline will be executed for all timesteps present in the initial PVD file. Please be aware that the number of pipeline filters and timesteps should be as limited as possible. Otherwise, please consider going to get a cup of coffee. + +* Load the module in ParaView: Tools > Manage Plugins.... > Load new > PVMohrCirclePlot + +If you start from a raw GEOS output, execute the following steps before moving on. +- First, consider removing some unnecessary timesteps manually from the PVD file in order to reduce the calculation time and resources used in the following steps. +- Load the data into ParaView, then apply the `PVGeosExtractMergeBlock*` plugin on it. +- Select the filter output that you want to consider for the Mohr's circle plot. + + +* Extract a few number of cells with the `ExtractSelection` ParaView Filter, then use the `MergeBlocks` ParaView Filter. +* Select the resulting mesh in the pipeline. +* Select Filters > 3- Geos Geomechanics > Plot Mohr's Circle. +* Select the cell Ids and time steps you want +* (Optional) Set rock cohesion and/or friction angle. +* Apply. -.. WARNING:: - Input vtk must contains a limited number of cells, Paraview may crash - otherwise. In addition, input pipeline should consist of the minimum number - of filters since this filter repeats the operations at every time steps. + +.. Note:: + After a first application, select again cells and time steps to display, then + * Apply again + * Click on `Refresh Data` (you may have to click twice to refresh the Python view correctly). """ @@ -89,55 +109,59 @@ """ ) @smproperty.input( name="Input", port_index=0 ) @smdomain.datatype( - dataTypes=[ "vtkUnstructuredGrid", "vtkMultiBlockDataSet" ], + dataTypes=[ "vtkUnstructuredGrid" ], composite_data_supported=False, ) class PVMohrCirclePlot( VTKPythonAlgorithmBase ): def __init__( self: Self ) -> None: - """Paraview plugin to plot Mohr's Circles of selected cells and times. + """ParaView plugin to plot Mohr's Circles of selected cells and times. Mohr's circles are plotted using a Python View. """ super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkDataObject" ) - # create a new PythonView - self.m_pythonView: Any = buildNewLayoutWithPythonView() + # Create a new PythonView + self.pythonView: Any = buildNewLayoutWithPythonView() + + # List of all cell ids in the mesh + self.cellIds: list[ str ] = [] - #: list of all cell ids - self.m_cellIds: list[ str ] = [] + # List of all time steps + self.timeSteps: npt.NDArray[ np.float64 ] = np.array( [] ) - #: cell selection object - self.m_cellIdsDAS: vtkDAS = vtkDAS() - self.m_cellIdsDAS.AddObserver( 0, createModifiedCallback( self ) ) + # Cell selection object + self.cellIdsDAS: vtkDAS = vtkDAS() + self.cellIdsDAS.AddObserver( 0, createModifiedCallback( self ) ) - #: list of all time steps - self.m_timeSteps: npt.NDArray[ np.float64 ] = np.array( [] ) + # Time steps selection object + self.timeStepsDAS: vtkDAS = vtkDAS() + self.timeStepsDAS.AddObserver( 0, createModifiedCallback( self ) ) - #: time steps selection object - self.m_timeStepsDAS: vtkDAS = vtkDAS() - self.m_timeStepsDAS.AddObserver( 0, createModifiedCallback( self ) ) + # Requested cell ids and time steps + self.requestedCellIds: list[ str ] = [] + self.requestedTimeStepsIndexes: list[ int ] = [] - #: list of all mohr circles - self.m_mohrCircles: list[ MohrCircle ] = [] + # List of mohr circles + self.mohrCircles: set[ MohrCircle ] = set() - #: failure envelop parameters - self.m_rockCohesion: float = DEFAULT_ROCK_COHESION - self.m_frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD + # Failure envelop parameters + self.rockCohesion: float = DEFAULT_ROCK_COHESION + self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD - #: stress convention (False for GEOS convention) - self.m_stressConvention: bool = False + # Stress convention (Geos: negative compression, Usual: positive) + self.useGeosStressConvention: bool = True - #: curve aspect options - the same variables are set for each selected curve - self.m_circleIdUsed: str = "" - self.m_color: tuple[ float, float, float ] = ( 0.0, 0.0, 0.0 ) - self.m_lineStyle: str = LineStyleEnum.SOLID.optionValue - self.m_lineWidth: float = 1.0 - self.m_markerStyle: str = MarkerStyleEnum.NONE.optionValue - self.m_markerSize: float = 1.0 + # Curve aspect options - the same variables are set for each selected curve + self.circleIdUsed: str = "" + self.color: tuple[ float, float, float ] = ( 0.0, 0.0, 0.0 ) + self.lineStyle: str = LineStyleEnum.SOLID.optionValue + self.lineWidth: float = 1.0 + self.markerStyle: str = MarkerStyleEnum.NONE.optionValue + self.markerSize: float = 1.0 - #: figure user choices - self.m_userChoices: dict[ str, Any ] = { + # Figure user choices + self.userChoices: dict[ str, Any ] = { "xAxis": "Normal stress", "yAxis": "Shear stress", "stressUnit": 0, @@ -159,52 +183,30 @@ def __init__( self: Self ) -> None: "limMaxY": None, } - #: request data processing step - incremented each time RequestUpdateExtent is called - self.m_requestDataStep: int = -1 - - #: logger - self.m_logger: Logger = getLogger( "Mohr's Circle Analysis Filter" ) - - def getUserChoices( self: Self ) -> dict[ str, Any ]: - """Access the m_userChoices attribute. - - Returns: - dict[str] : the user choices for the figure. - """ - return self.m_userChoices - - def getCircleIds( self: Self ) -> list[ str ]: - """Get circle ids to plot. + # Request data processing step - incremented each time RequestUpdateExtent is called + self.requestDataStep: int = -1 - Returns: - list[str]: list of circle ids to plot. - """ - cellIds: list[ str ] = pvt.getArrayChoices( self.a01GetCellIdsDAS() ) - timeSteps: list[ str ] = pvt.getArrayChoices( self.a02GetTimestepsToPlot() ) - return [ mcf.getMohrCircleId( cellId, timeStep ) for timeStep in timeSteps for cellId in cellIds ] + # Logger + self.logger: logging.Logger = logging.getLogger( "MohrCircle" ) + self.logger.setLevel( logging.INFO ) + if not self.logger.hasHandlers(): + handler = VTKHandler() + handler.setFormatter( CustomLoggerFormatter( False ) ) - def defineCurvesAspect( self: Self ) -> None: - """Add curve aspect parameters according to user choices.""" - self.m_userChoices[ "curvesAspect" ][ self.m_circleIdUsed ] = { - "color": self.m_color, - "linestyle": self.m_lineStyle, - "linewidth": self.m_lineWidth, - "marker": self.m_markerStyle, - "markersize": self.m_markerSize, - } + self.logger.addHandler( handler ) @smproperty.xml( """ - Recompute all the Mohr's circles at all time steps and display - selected ones. + Recompute Mohr's circles for requested time steps and cell ids. """ ) def a00RefreshData( self: Self ) -> None: - """Reset self.m_requestDataStep to reload data from all time steps.""" - self.m_requestDataStep = -1 + """Reset self.requestDataStep to reload data from all time steps.""" + self.requestDataStep = -1 + self.logger.info( "Recomputing data for selected time steps and cell ids." ) self.Modified() @smproperty.dataarrayselection( name="CellIdToPlot" ) @@ -212,18 +214,18 @@ def a01GetCellIdsDAS( self: Self ) -> vtkDAS: """Get selected cell ids to plot. Returns: - vtkDataArraySelection: selected cell ids. + vtkDataArraySelection: Selected cell ids. """ - return self.m_cellIdsDAS + return self.cellIdsDAS @smproperty.dataarrayselection( name="TimeStepsToPlot" ) def a02GetTimestepsToPlot( self: Self ) -> vtkDAS: """Get selected time steps to plot. Returns: - vtkDataArraySelection: selected time steps. + vtkDataArraySelection: Selected time steps. """ - return self.m_timeStepsDAS + return self.timeStepsDAS @smproperty.xml( """ @@ -243,9 +245,9 @@ def b01SetCohesion( self: Self, value: float ) -> None: """Set rock cohesion. Args: - value (float): rock cohesion (Pa) + value (float): Rock cohesion (Pa). """ - self.m_rockCohesion = value + self.rockCohesion = value self.Modified() @smproperty.doublevector( @@ -257,9 +259,9 @@ def b02SetFrictionAngle( self: Self, value: float ) -> None: """Set friction angle. Args: - value (float): friction angle (°). + value (float): Friction angle (°). """ - self.m_frictionAngle = value * np.pi / 180.0 + self.frictionAngle = value * np.pi / 180.0 self.Modified() @smproperty.xml( """ None: """Set stress unit. Args: - choice (int): stress unit index in Pressure enum. + choice (int): Stress unit index in Pressure enum. """ - self.m_userChoices[ "stressUnit" ] = choice + self.userChoices[ "stressUnit" ] = choice self.Modified() @smproperty.intvector( name="StressConventionForCompression", - label="Change stress Convention", - default_values=0, + label="Use GEOS stress Convention", + default_values=1, ) @smdomain.xml( """""" ) - def b05SetStressCompressionConvention( self: Self, boolean: bool ) -> None: + def b05SetStressCompressionConvention( self: Self, useGeosConvention: bool ) -> None: """Set stress compression convention in plots. Args: - boolean (bool): False is same as Geos convention, True is usual - geomechanical convention. + useGeosConvention (bool): True is Geos convention, False is usual geomechanical convention. """ - # need to convert Geos results if use the usual convention - self.m_stressConvention = boolean + # Specify if data is from GEOS + self.useGeosStressConvention = useGeosConvention self.Modified() @smproperty.intvector( name="AnnotateCircles", label="Annotate Circles", default_values=1 ) @@ -305,9 +306,10 @@ def b06SetAnnotateCircles( self: Self, boolean: bool ) -> None: """Set option to add annotatations to circles. Args: - boolean (bool): user choce. + boolean (bool): True to annotate circles, False otherwise. + Default is True. """ - self.m_userChoices[ "annotateCircles" ] = boolean + self.userChoices[ "annotateCircles" ] = boolean self.Modified() @smproperty.intvector( name="Minorticks", label="Minorticks", default_values=0 ) @@ -316,9 +318,10 @@ def b07SetMinorticks( self: Self, boolean: bool ) -> None: """Set option to display minor ticks. Args: - boolean (bool): user choice. + boolean (bool): True to display the minor ticks, False otherwise. + Defaults is False. """ - self.m_userChoices[ "minorticks" ] = boolean + self.userChoices[ "minorticks" ] = boolean self.Modified() @smproperty.xml( """ None: """Set option to modify legend and title. Args: - boolean (bool): user choice. + boolean (bool): True to modify the title and legend, False otherwise. + Defaults is False. """ - self.m_userChoices[ "displayTitle" ] = boolean - self.m_modifyTitleAndLegend = boolean + self.userChoices[ "displayTitle" ] = boolean + self.modifyTitleAndLegend = boolean @smproperty.stringvector( name="Title", default_values="Mohr's circle" ) def c01SetTitlePlot( self: Self, title: str ) -> None: """Set title. Args: - title (str): title. + title (str): Requested title. Defaults is "Mohr's circle". """ - self.m_userChoices[ "title" ] = title + self.userChoices[ "title" ] = title self.Modified() @smproperty.intvector( name="Title Style", label="Title Style", default_values=0 ) @@ -359,10 +363,10 @@ def c02SetTitleStyle( self: Self, value: int ) -> None: """Set title font style. Args: - value (int): title font style index in FontStyleEnum. + value (int): Title font style index in FontStyleEnum. """ choice = list( FontStyleEnum )[ value ] - self.m_userChoices[ "titleStyle" ] = choice.optionValue + self.userChoices[ "titleStyle" ] = choice.optionValue self.Modified() @smproperty.intvector( name="Title Weight", label="Title Weight", default_values=1 ) @@ -371,10 +375,10 @@ def c03SetTitleWeight( self: Self, value: int ) -> None: """Set title font weight. Args: - value (int): title font weight index in FontWeightEnum. + value (int): Title font weight index in FontWeightEnum. """ choice = list( FontWeightEnum )[ value ] - self.m_userChoices[ "titleWeight" ] = choice.optionValue + self.userChoices[ "titleWeight" ] = choice.optionValue self.Modified() @smproperty.intvector( name="Title Size", label="Title Size", default_values=12 ) @@ -383,9 +387,9 @@ def c04SetTitleSize( self: Self, size: float ) -> None: """Set title font size. Args: - size (float): title font size between 1 and 50. + size (float): Title font size between 1 and 50. """ - self.m_userChoices[ "titleSize" ] = size + self.userChoices[ "titleSize" ] = size self.Modified() @smproperty.xml( """ @@ -407,10 +411,10 @@ def d01SetLegendPosition( self: Self, value: int ) -> None: """Set legend position. Args: - value (int): legend position index in LegendLocationEnum. + value (int): Legend position index in LegendLocationEnum. """ choice = list( LegendLocationEnum )[ value ] - self.m_userChoices[ "legendPosition" ] = choice.optionValue + self.userChoices[ "legendPosition" ] = choice.optionValue self.Modified() @smproperty.intvector( name="LegendSize", label="Legend Size", default_values=10 ) @@ -419,9 +423,9 @@ def d02SetLegendSize( self: Self, size: float ) -> None: """Set legend font size. Args: - size (float): legend font size between 1 and 50. + size (float): Legend font size between 1 and 50. """ - self.m_userChoices[ "legendSize" ] = size + self.userChoices[ "legendSize" ] = size self.Modified() @smproperty.xml( """ @@ -441,9 +445,10 @@ def e01SetCustomAxisLim( self: Self, boolean: bool ) -> None: """Set option to define axis limits. Args: - boolean (bool): user choice. + boolean (bool): True to define manually the axis limits, False otherwise. + Defaults is False. """ - self.m_userChoices[ "customAxisLim" ] = boolean + self.userChoices[ "customAxisLim" ] = boolean self.Modified() @smproperty.doublevector( name="LimMinX", label="X min", default_values=-1e36 ) @@ -456,7 +461,7 @@ def e02LimMinX( self: Self, value: float ) -> None: value2: Union[ float, None ] = value if value2 == -1e36: value2 = None - self.m_userChoices[ "limMinX" ] = value2 + self.userChoices[ "limMinX" ] = value2 self.Modified() @smproperty.doublevector( name="LimMaxX", label="X max", default_values=1e36 ) @@ -469,7 +474,7 @@ def e03LimMaxX( self: Self, value: float ) -> None: value2: Union[ float, None ] = value if value2 == 1e36: value2 = None - self.m_userChoices[ "limMaxX" ] = value2 + self.userChoices[ "limMaxX" ] = value2 self.Modified() @smproperty.doublevector( name="LimMinY", label="Y min", default_values=-1e36 ) @@ -482,7 +487,7 @@ def e04LimMinY( self: Self, value: float ) -> None: value2: Union[ float, None ] = value if value2 == -1e36: value2 = None - self.m_userChoices[ "limMinY" ] = value2 + self.userChoices[ "limMinY" ] = value2 self.Modified() @smproperty.doublevector( name="LimMaxY", label="Y max", default_values=1e36 ) @@ -495,7 +500,7 @@ def e05LimMaxY( self: Self, value: float ) -> None: value2: Union[ float, None ] = value if value2 == 1e36: value2 = None - self.m_userChoices[ "limMaxY" ] = value2 + self.userChoices[ "limMaxY" ] = value2 self.Modified() @smproperty.xml( """ None: """Set option to modify curve aspect. Args: - boolean (bool): user choice. + boolean (bool): True to modify curve aspect, False otherwise. + Defaults is False. """ - self.m_modifyCurvesAspect = boolean + self.modifyCurvesAspect = boolean @smproperty.stringvector( name="CurvesInfo", information_only="1" ) def f02GetCurveNames( self: Self ) -> list[ str ]: """Get curves to modify. Returns: - list[str]: curves to modify + list[str]: Curves to modify """ - circleIds: list[ str ] = self.getCircleIds() + circleIds: list[ str ] = self._getCircleIds() return [ FAILURE_ENVELOPE ] + circleIds @smproperty.stringvector( name="CurveToModify", label="Curve name", number_of_elements="1" ) @@ -541,9 +547,9 @@ def f03SetCellID( self: Self, value: str ) -> None: """Set circle ids to use. Args: - value (str): circle ids. + value (str): Circle ids. """ - self.m_circleIdUsed = value + self.circleIdUsed = value self.Modified() @smproperty.intvector( name="LineStyle", label="Line Style", default_values=1 ) @@ -552,10 +558,10 @@ def f04SetLineStyle( self: Self, value: int ) -> None: """Set line style. Args: - value (int): line style index in LineStyleEnum + value (int): Line style index in LineStyleEnum. """ choice = list( LineStyleEnum )[ value ] - self.m_lineStyle = choice.optionValue + self.lineStyle = choice.optionValue self.Modified() @smproperty.doublevector( name="LineWidth", default_values=1.0 ) @@ -564,9 +570,9 @@ def f05SetLineWidth( self: Self, value: float ) -> None: """Set line width. Args: - value (float): line width between 1 and 10. + value (float): Line width between 1 and 10. """ - self.m_lineWidth = value + self.lineWidth = value self.Modified() @smproperty.intvector( name="MarkerStyle", label="Marker Style", default_values=0 ) @@ -575,10 +581,10 @@ def f06SetMarkerStyle( self: Self, value: int ) -> None: """Set marker style. Args: - value (int): Marker style index in MarkerStyleEnum + value (int): Marker style index in MarkerStyleEnum. """ choice = list( MarkerStyleEnum )[ value ] - self.m_markerStyle = choice.optionValue + self.markerStyle = choice.optionValue self.Modified() @smproperty.doublevector( name="MarkerSize", default_values=1.0 ) @@ -587,9 +593,9 @@ def f07SetMarkerSize( self: Self, value: float ) -> None: """Set marker size. Args: - value (float): size of markers between 1 and 30. + value (float): Size of markers between 1 and 30. """ - self.m_markerSize = value + self.markerSize = value self.Modified() @smproperty.xml( """ @@ -617,7 +623,7 @@ def f09SetColor( self: Self, value0: float, value1: float, value2: float ) -> No value1 (float): Green color between 0 and 1. value2 (float): Blue color between 0 and 1. """ - self.m_color = ( value0, value1, value2 ) + self.color = ( value0, value1, value2 ) self.Modified() @smproperty.xml( """ list[ MohrCircle ]: + ) -> set[ MohrCircle ]: """Create mohr circles of all cells at the current time step. Args: @@ -803,21 +811,66 @@ def createMohrCirclesAtTimeStep( Returns: list[MohrCircle]: list of MohrCircles for the current time step. """ - # get mesh and merge if needed - meshMerged: vtkUnstructuredGrid = mergeBlocks( mesh ) - - stressArray: npt.NDArray[ np.float64 ] = getArrayInObject( meshMerged, + # Get effective stress array + stressArray: npt.NDArray[ np.float64 ] = getArrayInObject( mesh, GeosMeshOutputsEnum.STRESS_EFFECTIVE.attributeName, False ) - return mcf.createMohrCircleAtTimeStep( stressArray, self.m_cellIds, str( currentTimeStep ), - self.m_stressConvention ) + # Get stress convention + stressConvention = StressConventionEnum.GEOS_STRESS_CONVENTION if self.useGeosStressConvention else StressConventionEnum.COMMON_STRESS_CONVENTION + + # Get the cell IDs requested by the user + self._updateRequestedCellIds() - def filterMohrCircles( self: Self ) -> list[ MohrCircle ]: + return mcf.createMohrCircleAtTimeStep( stressArray, self.requestedCellIds, str( currentTimeStep ), + stressConvention ) + + def _filterMohrCircles( self: Self ) -> list[ MohrCircle ]: """Filter the list of all MohrCircle to get those to plot. Returns: list[MohrCircle]: list of MohrCircle to plot. """ - # circle ids to plot - circleIds: list[ str ] = self.getCircleIds() - return [ mohrCircle for mohrCircle in self.m_mohrCircles if mohrCircle.getCircleId() in circleIds ] + # Circle ids to plot + circleIds: list[ str ] = self._getCircleIds() + return [ mohrCircle for mohrCircle in self.mohrCircles if mohrCircle.getCircleId() in circleIds ] + + def _updateRequestedTimeSteps( self: Self ) -> None: + """Update the requestedTimeStepsIndexes attribute from user choice.""" + requestedTimeSteps: list[ str ] = pvt.getArrayChoices( self.a02GetTimestepsToPlot() ) + + self.requestedTimeStepsIndexes = [ + pvt.getTimeStepIndex( float( ts ), self.timeSteps ) for ts in requestedTimeSteps + ] + + def _updateRequestedCellIds( self: Self ) -> None: + """Update the requestedCellIds attribute from user choice.""" + self.requestedCellIds = pvt.getArrayChoices( self.a01GetCellIdsDAS() ) + + def _getUserChoices( self: Self ) -> dict[ str, Any ]: + """Access the userChoices attribute. + + Returns: + dict[str, Any] : the user choices for the figure. + """ + return self.userChoices + + def _getCircleIds( self: Self ) -> list[ str ]: + """Get circle ids to plot. + + Returns: + list[str]: list of circle ids to plot. + """ + cellIds: list[ str ] = pvt.getArrayChoices( self.a01GetCellIdsDAS() ) + timeSteps: list[ str ] = pvt.getArrayChoices( self.a02GetTimestepsToPlot() ) + + return [ mcf.getMohrCircleId( cellId, timeStep ) for timeStep in timeSteps for cellId in cellIds ] + + def _defineCurvesAspect( self: Self ) -> None: + """Add curve aspect parameters according to user choices.""" + self.userChoices[ "curvesAspect" ][ self.circleIdUsed ] = { + "color": self.color, + "linestyle": self.lineStyle, + "linewidth": self.lineWidth, + "marker": self.markerStyle, + "markersize": self.markerSize, + } diff --git a/geos-pv/src/geos/pv/utils/mohrCircles/__init__.py b/geos-pv/src/geos/pv/utils/mohrCircles/__init__.py new file mode 100644 index 00000000..2627d2a6 --- /dev/null +++ b/geos-pv/src/geos/pv/utils/mohrCircles/__init__.py @@ -0,0 +1,6 @@ +import warnings + +warnings.filterwarnings( "ignore", message="The value of the smallest subnormal for" ) + +MOHR_CIRCLE_PATH: str = "src/geos/pv/utils/mohrCircles/" +MOHR_CIRCLE_ANALYSIS_MAIN = "mainMohrCircles.py" diff --git a/geos-posp/src/geos_posp/visu/mohrCircles/functionsMohrCircle.py b/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py similarity index 60% rename from geos-posp/src/geos_posp/visu/mohrCircles/functionsMohrCircle.py rename to geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py index 0d4042e3..fafe7228 100644 --- a/geos-posp/src/geos_posp/visu/mohrCircles/functionsMohrCircle.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py @@ -1,27 +1,37 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. -# SPDX-FileContributor: Alexandre Benedicto +# SPDX-FileContributor: Alexandre Benedicto, Paloma Martinez import os from typing import Any - +from enum import Enum import numpy as np import numpy.typing as npt from geos.geomechanics.model.MohrCircle import MohrCircle from geos.geomechanics.model.MohrCoulomb import MohrCoulomb -from geos_posp.visu.mohrCircles import ( +from geos.pv.utils.mohrCircles import ( MOHR_CIRCLE_ANALYSIS_MAIN, MOHR_CIRCLE_PATH, ) __doc__ = """ -functionsMohrCircle module provides a set of utilities to instanciate Mohr's +The functionsMohrCircle module provides a set of utilities to instantiate Mohr's circles and Mohr-Coulomb failure envelope. """ +class StressConventionEnum( Enum ): + """Utility Enum to define the effective stress convention used for compression. + + The usual convention considers the compression as positive. + With GEOS convention, the compression is considered negative. + """ + GEOS_STRESS_CONVENTION = -1.0 + COMMON_STRESS_CONVENTION = 1.0 + + def buildPythonViewScript( - dir_path: str, + dirpath: str, mohrCircles: list[ MohrCircle ], rockCohesion: float, frictionAngle: float, @@ -29,23 +39,19 @@ def buildPythonViewScript( ) -> str: """Builds the Python script used to launch the Python View. - The script is returned as a string to be then injected in the Python - View. + The script is returned as a string to be then injected in the Python View. Args: - dir_path (str): directory path + dirpath (str): Root directory path for the script creation. + mohrCircles (list[MohrCircle]): List of MohrCircle objects. + rockCohesion (float): Rock cohesion (Pa). + frictionAngle (float): Friction angle (rad). + userChoices (dict[str, Any]): Dictionary of user plot parameters. - mohrCircles (list[MohrCircle]): list of MohrCircle objects - - rockCohesion (float): rock cohesion (Pa) - - frictionAngle (float): friction angle (rad) - - userChoices (dict[str, Any]): dictionnary of user plot parameters Returns: str: Complete Python View script. """ - pathPythonViewScript: str = os.path.join( dir_path, MOHR_CIRCLE_PATH, MOHR_CIRCLE_ANALYSIS_MAIN ) + pathPythonViewScript: str = os.path.join( dirpath, MOHR_CIRCLE_PATH, MOHR_CIRCLE_ANALYSIS_MAIN ) mohrCircleParams: list[ tuple[ str, float, float, float ] ] = [ ( mohrCircle.getCircleId(), *( mohrCircle.getPrincipalComponents() ) ) @@ -66,15 +72,14 @@ def findAnnotateTuples( mohrCircle: MohrCircle, ) -> tuple[ str, str, tuple[ flo """Get the values and location of min and max normal stress or Mohr's circle. Args: - mohrCircle (MohrCircle): input Mohr's circle - - maxTau (float): max shear stress + mohrCircle (MohrCircle): Mohr's circle to consider. + maxTau (float): Max shear stress. Returns: - tuple[str, str, tuple[float, float], tuple[float, float]]: labels and - location of labels. + tuple[str, str, tuple[float, float], tuple[float, float]]: Labels and + location of labels. """ - p3, p2, p1 = mohrCircle.getPrincipalComponents() + p3, _, p1 = mohrCircle.getPrincipalComponents() xMaxDisplay: str = f"{p1:.2E}" xMinDisplay: str = f"{p3:.2E}" yPosition: float = 0.0 @@ -84,15 +89,14 @@ def findAnnotateTuples( mohrCircle: MohrCircle, ) -> tuple[ str, str, tuple[ flo def getMohrCircleId( cellId: str, timeStep: str ) -> str: - """Get Mohr's circle ID from cell id and time step. + """Get Mohr's circle ID from cell ID and time step. Args: - cellId (str): cell ID - - timeStep (str): time step. + cellId (str): Cell ID. + timeStep (str): Time step. Returns: - str: Mohr's circle ID + str: Mohr's circle ID. """ return f"Cell_{cellId}@{timeStep}" @@ -101,32 +105,33 @@ def createMohrCircleAtTimeStep( stressArray: npt.NDArray[ np.float64 ], cellIds: list[ str ], timeStep: str, - convention: bool, -) -> list[ MohrCircle ]: + convention: StressConventionEnum, +) -> set[ MohrCircle ]: """Create MohrCircle object(s) at a given time step for all cell ids. Args: - stressArray (npt.NDArray[np.float64]): stress numpy array - - cellIds (list[str]): list of cell ids + stressArray (npt.NDArray[np.float64]): Stress numpy array + cellIds (list[str]): List of cell ids + timeStep (str): Time step + convention (StressConventionEnum): Convention used for compression. - timeStep (str): time step - - convention (bool): convention used for compression. - * False is Geos convention (compression is negative) - * True is usual convention (compression is positive) + Raises: + ValueError: Stress array must consists of 6 components. Returns: - list[MohrCircle]: list of MohrCircle objects. + set[MohrCircle]: Set of MohrCircle objects. """ - assert stressArray.shape[ 1 ] == 6, "Stress vector must be of size 6." - mohrCircles: list[ MohrCircle ] = [] - sign: float = 1.0 if convention else -1.0 + if stressArray.shape[ 1 ] != 6: + raise ValueError( "Expected 6 components for stress array, not {stressArray.shape[ 1 ]}.\n \ + Cannot proceed with the creation of Mohr circles." ) + + mohrCircles: set[ MohrCircle ] = set() for i, cellId in enumerate( cellIds ): ide: str = getMohrCircleId( cellId, timeStep ) mohrCircle: MohrCircle = MohrCircle( ide ) - mohrCircle.computePrincipalComponents( stressArray[ i ] * sign ) - mohrCircles.append( mohrCircle ) + mohrCircle.computePrincipalComponents( stressArray[ i ] * convention.value ) + mohrCircles.add( mohrCircle ) + return mohrCircles @@ -135,11 +140,10 @@ def createMohrCirclesFromPrincipalComponents( """Create Mohr's circle objects from principal components. Args: - mohrCircleParams (list[tuple[str, float, float, float]]): list of Mohr's - circle parameters + mohrCircleParams (list[tuple[str, float, float, float]]): List of Mohr's circle parameters Returns: - list[MohrCircle]: list of Mohr's circle objects. + list[MohrCircle]: List of Mohr's circle objects. """ mohrCircles: list[ MohrCircle ] = [] for circleId, p3, p2, p1 in mohrCircleParams: @@ -153,9 +157,8 @@ def createMohrCoulombEnvelope( rockCohesion: float, frictionAngle: float ) -> Mo """Create MohrCoulomb object from user parameters. Args: - rockCohesion (float): rock cohesion (Pa). - - frictionAngle (float): friction angle in radian. + rockCohesion (float): Rock cohesion (Pa). + frictionAngle (float): Friction angle in radian. Returns: MohrCoulomb: MohrCoulomb object. diff --git a/geos-posp/src/geos_posp/visu/mohrCircles/mainMohrCircles.py b/geos-pv/src/geos/pv/utils/mohrCircles/mainMohrCircles.py similarity index 82% rename from geos-posp/src/geos_posp/visu/mohrCircles/mainMohrCircles.py rename to geos-pv/src/geos/pv/utils/mohrCircles/mainMohrCircles.py index 7eb27a13..2765cd7d 100644 --- a/geos-posp/src/geos_posp/visu/mohrCircles/mainMohrCircles.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/mainMohrCircles.py @@ -7,8 +7,8 @@ import matplotlib.pyplot as plt from paraview import python_view - import geos_posp.visu.mohrCircles.functionsMohrCircle as mcf - import geos_posp.visu.mohrCircles.plotMohrCircles as pmc + import geos.pv.utils.mohrCircles.functionsMohrCircle as mcf + import geos.pv.utils.mohrCircles.plotMohrCircles as pmc plt.close() @@ -30,9 +30,11 @@ ) def setup_data( view ) -> None: # noqa + """Setup data.""" pass def render( view, width: int, height: int ): # noqa + """Render method.""" fig.set_size_inches( float( width ) / 100.0, float( height ) / 100.0 ) return python_view.figure_to_image( fig ) diff --git a/geos-posp/src/geos_posp/visu/mohrCircles/plotMohrCircles.py b/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py similarity index 78% rename from geos-posp/src/geos_posp/visu/mohrCircles/plotMohrCircles.py rename to geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py index d7c21c6c..28cc9e49 100644 --- a/geos-posp/src/geos_posp/visu/mohrCircles/plotMohrCircles.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py @@ -1,22 +1,18 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. -# SPDX-FileContributor: Alexandre Benedicto, Martin Lemay +# SPDX-FileContributor: Alexandre Benedicto, Martin Lemay, Paloma Martinez +import numpy as np from typing import Any - import matplotlib.pyplot as plt # type: ignore[import-untyped] -import numpy as np import numpy.typing as npt -from geos.geomechanics.model.MohrCircle import MohrCircle -from geos.geomechanics.model.MohrCoulomb import MohrCoulomb -from geos.utils.enumUnits import Pressure, Unit, convert -from geos.utils.GeosOutputsConstants import FAILURE_ENVELOPE + from matplotlib import ticker from matplotlib.axes import Axes # type: ignore[import-untyped] from matplotlib.figure import Figure # type: ignore[import-untyped] from matplotlib.lines import Line2D # type: ignore[import-untyped] -import geos_posp.visu.mohrCircles.functionsMohrCircle as mcf -from geos_posp.visu.PVUtils.matplotlibOptions import ( +import geos.pv.utils.mohrCircles.functionsMohrCircle as mcf +from geos.pv.pyplotUtils.matplotlibOptions import ( FontStyleEnum, FontWeightEnum, LegendLocationEnum, @@ -24,6 +20,11 @@ MarkerStyleEnum, ) +from geos.geomechanics.model.MohrCircle import MohrCircle +from geos.geomechanics.model.MohrCoulomb import MohrCoulomb +from geos.utils.enumUnits import Pressure, Unit, convert +from geos.utils.GeosOutputsConstants import FAILURE_ENVELOPE + __doc__ = """ plotMohrCircles module provides a set of functions to plot multiple Mohr's circles and a failure envelope from a list of MohrCircle and MohrCoulomb @@ -36,31 +37,28 @@ def createMohrCirclesFigure( mohrCircles: list[ MohrCircle ], mohrCoulomb: MohrC """Create Mohr's circle figure. Args: - mohrCircles (list[MohrCircle]): list of MohrCircle objects. - - mohrCoulomb (MohrCoulomb): MohrCoulomb object defining the failure - envelope. - - userChoices (dict[str, Any]): dictionnary to define figure properties. + mohrCircles (list[MohrCircle]): List of MohrCircle objects. + mohrCoulomb (MohrCoulomb): MohrCoulomb object defining the failure envelope. + userChoices (dict[str, Any]): Dictionnary to define figure properties. Returns: Figure: Figure object """ plt.close() - # create figure + # Create figure fig, ax = plt.subplots( constrained_layout=True ) - # plot Mohr's Circles + # Plot Mohr's Circles curvesAspect: dict[ str, Any ] = userChoices.get( "curvesAspect", {} ) annotate: bool = userChoices.get( "annotateCircles", False ) _plotMohrCircles( ax, mohrCircles, curvesAspect, annotate ) - # plot Mohr Coulomb failure envelop + # Plot Mohr Coulomb failure envelop failureEnvelopeAspect: dict[ str, Any ] = curvesAspect.get( FAILURE_ENVELOPE, {} ) _plotMohrCoulomb( ax, mohrCoulomb, failureEnvelopeAspect ) - # set user preferences + # Set user preferences _setUserChoices( ax, userChoices ) return fig @@ -76,20 +74,16 @@ def _plotMohrCircles( Args: ax (Axes): Axes where to plot Mohr's circles - - mohrCircles (list[MohrCircle]): list of MohrCircle objects to plot. - - circlesAspect (dict[str, dict[str, Any]]): dictionnary defining Mohr's - circle line properties. - - annotate (bool): if True, display min and max normal stress. + mohrCircles (list[MohrCircle]): List of MohrCircle objects to plot. + circlesAspect (dict[str, dict[str, Any]]): Dictionnary defining Mohr's circle line properties. + annotate (bool): If True, display min and max normal stress. """ nbPts: int = 361 ang: npt.NDArray[ np.float64 ] = np.linspace( 0.0, np.pi, nbPts ).astype( np.float64 ) for mohrCircle in mohrCircles: radius: float = mohrCircle.getCircleRadius() - xCoords = mohrCircle.getCircleCenter() + radius * np.cos( ang ) - yCoords = radius * ( np.sin( ang ) ) + xCoords: float = mohrCircle.getCircleCenter() + radius * np.cos( ang ) + yCoords: npt.NDArray[ np.float64 ] = radius * ( np.sin( ang ) ) label: str = mohrCircle.getCircleId() p: list[ Line2D ] # plotted lines to get the color later @@ -126,12 +120,8 @@ def _plotMohrCoulomb( ax: Axes, mohrCoulomb: MohrCoulomb, curvesAspect: dict[ st Args: ax (Axes): Axes where to plot the failure envelope. - - mohrCoulomb (MohrCoulomb): MohrCoulomb object to define failure envelope - parameters. - - curvesAspect (dict[str, Any]): dictionnary defining line properties of - the failure envelope. + mohrCoulomb (MohrCoulomb): MohrCoulomb object to define failure envelope parameters. + curvesAspect (dict[str, Any]): Line properties of the failure envelope. """ xmin, xmax = ax.get_xlim() principalStresses, shearStress = mohrCoulomb.computeFailureEnvelop( xmax ) @@ -157,16 +147,15 @@ def _setUserChoices( ax: Axes, userChoices: dict[ str, Any ] ) -> None: Args: ax (Axes): Axes object to modify. - - userChoices (dict[str, Any]): dictionnary of user-defined properties. + userChoices (dict[str, Any]): User-defined properties. """ _updateAxis( ax, userChoices ) - # set title properties + # Set title properties if userChoices.get( "displayTitle", False ): updateTitle( ax, userChoices ) - # set legend + # Set legend if userChoices.get( "displayLegend", False ): _updateLegend( ax, userChoices ) @@ -178,34 +167,33 @@ def _updateAxis( ax: Axes, userChoices: dict[ str, Any ] ) -> None: """Update axis ticks and labels. Args: - ax (Axes): axes object. - - userChoices (dict[str, Any]): user parameters. + ax (Axes): Axes object. + userChoices (dict[str, Any]): User-defined properties. """ - # update axis labels + # Update axis labels xlabel: str = userChoices.get( "xAxis", "Normal stress" ) - ylabel: str = userChoices.get( "xAyAxisxis", "Shear stress" ) + ylabel: str = userChoices.get( "yAxis", "Shear stress" ) - # get unit + # Get unit unitChoice: int = userChoices.get( "stressUnit", 0 ) unitObj: Unit = list( Pressure )[ unitChoice ].value unitLabel: str = unitObj.unitLabel - # change displayed units + # Change displayed units xlabel += f" ({unitLabel})" ylabel += f" ({unitLabel})" ax.set_xlabel( xlabel ) ax.set_ylabel( ylabel ) - # function to do conversion and set format + # Function to do conversion and set format def _tickFormatterFunc( x: float, pos: str ) -> str: return f"{convert(x, unitObj):.2E}" - # apply formatting to xticks and yticks + # Apply formatting to xticks and yticks ax.xaxis.set_major_formatter( ticker.FuncFormatter( _tickFormatterFunc ) ) ax.yaxis.set_major_formatter( ticker.FuncFormatter( _tickFormatterFunc ) ) - # set axis properties + # Set axis properties ax.set_aspect( "equal", anchor="C" ) xmin, xmax = ax.get_xlim() ax.set_xlim( 0.0 ) @@ -220,14 +208,13 @@ def updateTitle( ax: Axes, userChoices: dict[ str, Any ] ) -> None: """Update title. Args: - ax (Axes): axes object. - - userChoices (dict[str, Any]): user parameters. + ax (Axes): Axes object. + userChoices (dict[str, Any]): User-defined properties. """ - title = userChoices.get( "title", "Mohr's Circles" ) - style = userChoices.get( "titleStyle", FontStyleEnum.NORMAL.optionValue ) - weight = userChoices.get( "titleWeight", FontWeightEnum.BOLD.optionValue ) - size = userChoices.get( "titleSize", 12 ) + title: str = userChoices.get( "title", "Mohr's Circles" ) + style: str = userChoices.get( "titleStyle", FontStyleEnum.NORMAL.optionValue ) + weight: str = userChoices.get( "titleWeight", FontWeightEnum.BOLD.optionValue ) + size: int = userChoices.get( "titleSize", 12 ) ax.set_title( title, fontstyle=style, weight=weight, fontsize=size ) @@ -235,9 +222,8 @@ def _updateLegend( ax: Axes, userChoices: dict[ str, Any ] ) -> None: """Update legend. Args: - ax (Axes): axes object. - - userChoices (dict[str, Any]): user parameters. + ax (Axes): Axes object. + userChoices (dict[str, Any]): User-defined properties. """ loc = userChoices.get( "legendPosition", LegendLocationEnum.BEST.optionValue ) size = userChoices.get( "legendSize", 10 ) @@ -248,9 +234,8 @@ def _updateAxisLimits( ax: Axes, userChoices: dict[ str, Any ] ) -> None: """Update axis limits. Args: - ax (Axes): axes object. - - userChoices (dict[str, Any]): user parameters. + ax (Axes): Axes object. + userChoices (dict[str, Any]): User-defined properties. """ xmin, xmax = ax.get_xlim() if userChoices.get( "limMinX" ) is not None: diff --git a/geos-pv/src/geos/pv/utils/paraviewTreatments.py b/geos-pv/src/geos/pv/utils/paraviewTreatments.py index 98ad009a..36aad6b1 100644 --- a/geos-pv/src/geos/pv/utils/paraviewTreatments.py +++ b/geos-pv/src/geos/pv/utils/paraviewTreatments.py @@ -2,6 +2,7 @@ # 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 logging from enum import Enum from typing import Any, Union @@ -9,18 +10,11 @@ import numpy.typing as npt import pandas as pd # type: ignore[import-untyped] -from packaging.version import Version - -# TODO: remove this condition when all codes are adapted for Paraview 6.0 -import vtk -if Version( vtk.__version__ ) >= Version( "9.5" ): - from vtkmodules.vtkFiltersParallel import vtkMergeBlocks -else: - from paraview.modules.vtkPVVTKExtensionsMisc import ( # type: ignore[import-not-found] - vtkMergeBlocks, ) from paraview.simple import ( # type: ignore[import-not-found] FindSource, GetActiveView, GetAnimationScene, GetDisplayProperties, GetSources, servermanager, ) +from paraview.detail.loghandler import ( # type: ignore[import-not-found] + VTKHandler, ) import vtkmodules.util.numpy_support as vnp from vtkmodules.vtkCommonCore import ( vtkDataArray, @@ -43,6 +37,8 @@ ComponentNameEnum, GeosMeshOutputsEnum, ) +from geos.utils.Logger import ( CustomLoggerFormatter ) +from geos.mesh.utils.multiblockModifiers import mergeBlocks # valid sources for Python view configurator # TODO: need to be consolidated @@ -461,7 +457,7 @@ def integrateSourceNames( sourceNames: set[ str ], arrayChoices: set[ str ] ) -> Args: sourceNames (set[str]): Name of sources found in ParaView pipeline. - arrayChoices (set[str]): Column names of the vtkdataarrayselection. + arrayChoices (set[str]): Column names of the vtkDataArraySelection. Returns: set[str]: [sourceName1__choice1, sourceName1__choice2, @@ -475,19 +471,31 @@ def integrateSourceNames( sourceNames: set[ str ], arrayChoices: set[ str ] ) -> return completeNames -def getVtkOriginalCellIds( mesh: Union[ vtkMultiBlockDataSet, vtkCompositeDataSet, vtkDataObject ] ) -> list[ str ]: +def getVtkOriginalCellIds( mesh: Union[ vtkMultiBlockDataSet, vtkCompositeDataSet, vtkDataObject ], + logger: Union[ logging.Logger, None ] = None ) -> list[ str ]: """Get vtkOriginalCellIds from a vtkUnstructuredGrid object. Args: - mesh (vtkMultiBlockDataSet|vtkCompositeDataSet|vtkDataObject): input mesh. + mesh (vtkMultiBlockDataSet|vtkCompositeDataSet|vtkDataObject): Input mesh. + logger(Union[logging.Logger, None], optional): A logger to manage the output messages. + Defaults to None, an internal logger is used. Returns: list[str]: ids of the cells. """ - # merge blocks for vtkCompositeDataSet - mesh2: vtkUnstructuredGrid = mergeFilterPV( mesh ) + if logger is None: + logger = logging.getLogger( "getVtkOriginalCellIds" ) + + if not logger.hasHandlers(): + handler = VTKHandler() + handler.setFormatter( CustomLoggerFormatter( False ) ) + logger.addHandler( handler ) + + # Merge blocks for vtkCompositeDataSet + mesh2: vtkUnstructuredGrid = mergeBlocks( mesh, logger=logger ) attributeName: str = GeosMeshOutputsEnum.VTK_ORIGINAL_CELL_ID.attributeName data: vtkCellData = mesh2.GetCellData() + assert data is not None, "Cell Data are undefined." assert bool( data.HasArray( attributeName ) ), f"Attribute {attributeName} is not in the mesh" @@ -556,7 +564,7 @@ def dataframeForEachTimestep( sourceName: str ) -> dict[ str, pd.DataFrame ]: source = FindSource( sourceName ) dataset: vtkDataObject = servermanager.Fetch( source ) assert dataset is not None, "Dataset is undefined." - dataset2: vtkUnstructuredGrid = mergeFilterPV( dataset ) + dataset2: vtkUnstructuredGrid = mergeBlocks( dataset ) time: str = str( animationScene.TimeKeeper.Time ) dfPerTimestep: dict[ str, pd.DataFrame ] = { time: vtkToDataframe( dataset2 ) } # then we iterate on the other timesteps of the source @@ -564,7 +572,8 @@ def dataframeForEachTimestep( sourceName: str ) -> dict[ str, pd.DataFrame ]: animationScene.GoToNext() source = FindSource( sourceName ) dataset = servermanager.Fetch( source ) - dataset2 = mergeFilterPV( dataset ) + # dataset2 = mergeFilterPV( dataset ) + dataset2 = mergeBlocks( dataset ) time = str( animationScene.TimeKeeper.Time ) dfPerTimestep[ time ] = vtkToDataframe( dataset2 ) return dfPerTimestep @@ -583,20 +592,3 @@ def getTimeStepIndex( time: float, timeSteps: npt.NDArray[ np.float64 ] ) -> int indexes: npt.NDArray[ np.int64 ] = np.where( np.isclose( timeSteps, time ) )[ 0 ] assert ( indexes.size > 0 ), f"Current time {time} does not exist in the selected object." return int( indexes[ 0 ] ) - - -def mergeFilterPV( input: vtkDataObject, ) -> vtkUnstructuredGrid: - """Apply Paraview merge block filter. - - Args: - input (vtkMultiBlockDataSet | vtkCompositeDataSet | vtkDataObject): composite - object to merge blocks - - Returns: - vtkUnstructuredGrid: merged block object - - """ - mergeFilter: vtkMergeBlocks = vtkMergeBlocks() - mergeFilter.SetInputData( input ) - mergeFilter.Update() - return mergeFilter.GetOutputDataObject( 0 ) diff --git a/geos-pv/tests/testsFunctionsGeosLogReader.py b/geos-pv/tests/testsFunctionsGeosLogReader.py index 24f9f8d2..cd615d15 100644 --- a/geos-pv/tests/testsFunctionsGeosLogReader.py +++ b/geos-pv/tests/testsFunctionsGeosLogReader.py @@ -15,7 +15,7 @@ if parent_dir_path not in sys.path: sys.path.append( parent_dir_path ) -from geos_posp.processing import geosLogReaderFunctions as utils +from geos.pv import geosLogReaderFunctions as lrf from geos.utils.enumUnits import Unit, getSIUnits @@ -25,14 +25,14 @@ def test_replaceSpecialCharactersWithWhitespace( self: Self ) -> None: """Test replaceSpecialCharactersWithWhitespace function.""" example: str = "hi '(_there(''&*$^,:;'" expected: str = "hi there " - obtained: str = utils.replaceSpecialCharactersWithWhitespace( example ) + obtained: str = lrf.replaceSpecialCharactersWithWhitespace( example ) self.assertEqual( expected, obtained ) def test_formatPropertyName( self: Self ) -> None: """Test formatPropertyName function.""" example: str = " Delta pressure min" expected: str = "DeltaPressureMin" - obtained: str = utils.formatPropertyName( example ) + obtained: str = lrf.formatPropertyName( example ) self.assertEqual( expected, obtained ) def test_extractRegion( self: Self ) -> None: @@ -40,7 +40,7 @@ def test_extractRegion( self: Self ) -> None: example: str = ( "Adding Object CellElementRegion named Reservoir from" " ObjectManager::Catalog." ) expected: str = "Reservoir" - obtained: str = utils.extractRegion( example ) + obtained: str = lrf.extractRegion( example ) self.assertEqual( expected, obtained ) def test_extractStatsName( self: Self ) -> None: @@ -48,7 +48,7 @@ def test_extractStatsName( self: Self ) -> None: example: str = ( "compflowStatistics, Reservoir: Pressure (min, average, max): " "2.86419e+07, 2.93341e+07, 3.006e+07 Pa" ) expected: str = "compflowStatistics" - obtained: str = utils.extractStatsName( example ) + obtained: str = lrf.extractStatsName( example ) self.assertEqual( expected, obtained ) def test_extractPhaseModel( self: Self ) -> None: @@ -56,7 +56,7 @@ def test_extractPhaseModel( self: Self ) -> None: example: str = ( " TableFunction: " "fluid_phaseModel1_PhillipsBrineDensity_table" ) expected: str = "PhillipsBrineDensity" - obtained: str = utils.extractPhaseModel( example ) + obtained: str = lrf.extractPhaseModel( example ) self.assertEqual( expected, obtained ) def test_buildPropertiesNameForPhases( self: Self ) -> None: @@ -64,7 +64,7 @@ def test_buildPropertiesNameForPhases( self: Self ) -> None: example_block: str = " Mobile phase mass" example_phases: list[ str ] = [ "CO2", "Water" ] expected: list[ str ] = [ " Mobile CO2 mass", " Mobile Water mass" ] - obtained: list[ str ] = utils.buildPropertiesNameForPhases( example_block, example_phases ) + obtained: list[ str ] = lrf.buildPropertiesNameForPhases( example_block, example_phases ) self.assertEqual( expected, obtained ) def test_buildPropertiesNameForComponents( self: Self ) -> None: @@ -76,7 +76,7 @@ def test_buildPropertiesNameForComponents( self: Self ) -> None: "Dissolved mass CO2 in Water", "Dissolved mass Water in Water", ] - obtained: list[ str ] = utils.buildPropertiesNameForComponents( example ) + obtained: list[ str ] = lrf.buildPropertiesNameForComponents( example ) self.assertEqual( expected, obtained ) def test_buildPropertiesNameNoPhases( self: Self ) -> None: @@ -84,14 +84,14 @@ def test_buildPropertiesNameNoPhases( self: Self ) -> None: example_name_block: str = " Delta pressure " example_extensions: str = "min, max)" expected: list[ str ] = [ " Delta pressure min", " Delta pressure max" ] - obtained: list[ str ] = utils.buildPropertiesNameNoPhases( example_name_block, example_extensions ) + obtained: list[ str ] = lrf.buildPropertiesNameNoPhases( example_name_block, example_extensions ) self.assertEqual( expected, obtained ) def test_buildPropertiesNameNoPhases2( self: Self ) -> None: """Test buildPropertiesNameNoPhases function.""" example: str = " Delta pressure " expected: list[ str ] = [ " Delta pressure " ] - obtained: list[ str ] = utils.buildPropertiesNameNoPhases( example ) + obtained: list[ str ] = lrf.buildPropertiesNameNoPhases( example ) self.assertEqual( expected, obtained ) def test_buildPropertiesNameFromGeosProperties( self: Self ) -> None: @@ -99,17 +99,17 @@ def test_buildPropertiesNameFromGeosProperties( self: Self ) -> None: examples_phases: list[ str ] = [ "CO2", "Water" ] example: str = " Pressure (min, average, max)" expected: list[ str ] = [ " Pressure min", " Pressure average", " Pressure max" ] - obtained: list[ str ] = utils.buildPropertiesNameFromGeosProperties( example, examples_phases ) + obtained: list[ str ] = lrf.buildPropertiesNameFromGeosProperties( example, examples_phases ) self.assertEqual( expected, obtained ) example = " Total dynamic pore volume" expected = [ " Total dynamic pore volume" ] - obtained = utils.buildPropertiesNameFromGeosProperties( example, examples_phases ) + obtained = lrf.buildPropertiesNameFromGeosProperties( example, examples_phases ) self.assertEqual( expected, obtained ) example = " Non-trapped phase mass (metric 1)" expected = [ " Non-trapped CO2 mass ", " Non-trapped Water mass " ] - obtained = utils.buildPropertiesNameFromGeosProperties( example, examples_phases ) + obtained = lrf.buildPropertiesNameFromGeosProperties( example, examples_phases ) self.assertEqual( expected, obtained ) example = " Dissolved component mass" @@ -119,7 +119,7 @@ def test_buildPropertiesNameFromGeosProperties( self: Self ) -> None: "Dissolved mass CO2 in Water", "Dissolved mass Water in Water", ] - obtained = utils.buildPropertiesNameFromGeosProperties( example, examples_phases ) + obtained = lrf.buildPropertiesNameFromGeosProperties( example, examples_phases ) self.assertEqual( expected, obtained ) example = " Component mass" @@ -129,7 +129,7 @@ def test_buildPropertiesNameFromGeosProperties( self: Self ) -> None: "Dissolved mass CO2 in Water", "Dissolved mass Water in Water", ] - obtained = utils.buildPropertiesNameFromGeosProperties( example, examples_phases ) + obtained = lrf.buildPropertiesNameFromGeosProperties( example, examples_phases ) self.assertEqual( expected, obtained ) def test_extractPropertiesFlow( self: Self ) -> None: @@ -141,13 +141,13 @@ def test_extractPropertiesFlow( self: Self ) -> None: "Reservoir__TrappedCO2Mass", "Reservoir__TrappedWaterMass", ] - obtained: list[ str ] = utils.extractPropertiesFlow( example_block, examples_phases ) + obtained: list[ str ] = lrf.extractPropertiesFlow( example_block, examples_phases ) self.assertEqual( expected, obtained ) example_block = ( "compflowStatistics, Reservoir: Phase mass:" " { 0, 1.01274e+14 } kg" ) expected = [ "Reservoir__CO2Mass", "Reservoir__WaterMass" ] - obtained = utils.extractPropertiesFlow( example_block, examples_phases ) + obtained = lrf.extractPropertiesFlow( example_block, examples_phases ) self.assertEqual( expected, obtained ) example_block = ( "compflowStatistics, Region1 (time 4320000 s): Pressure" @@ -158,14 +158,14 @@ def test_extractPropertiesFlow( self: Self ) -> None: "Region1__PressureAverage", "Region1__PressureMax", ] - obtained = utils.extractPropertiesFlow( example_block, examples_phases ) + obtained = lrf.extractPropertiesFlow( example_block, examples_phases ) self.assertEqual( expected, obtained ) def test_countNumberLines( self: Self ) -> None: """Test countNumberLines function.""" log1: str = os.path.join( dir_path, "Data/job_GEOS_825200.out" ) expected1: int = 24307 - obtained1: int = utils.countNumberLines( log1 ) + obtained1: int = lrf.countNumberLines( log1 ) self.assertEqual( expected1, obtained1 ) def test_extractValuesFlow( self: Self ) -> None: @@ -173,38 +173,38 @@ def test_extractValuesFlow( self: Self ) -> None: example: str = ( "compflowStatistics, Reservoir: Pressure (min, average, max):" " 1.25e+07, 1.25e+07, 1.25e+07 Pa" ) expected: list[ float ] = [ 1.25e07, 1.25e07, 1.25e07 ] - obtained: list[ float ] = utils.extractValuesFlow( example ) + obtained: list[ float ] = lrf.extractValuesFlow( example ) self.assertEqual( expected, obtained ) example = ( "compflowStatistics, Reservoir: Phase dynamic pore volumes:" " { 0, 6.61331e+07 } rm^3" ) expected = [ 0.0, 6.61331e07 ] - obtained = utils.extractValuesFlow( example ) + obtained = lrf.extractValuesFlow( example ) self.assertEqual( expected, obtained ) example = ( "compflowStatistics, Reservoir: Dissolved component mass:" " { { 0, 0 }, { 0, 6.38235e+10 } } kg" ) expected = [ 0.0, 0.0, 0.0, 6.38235e10 ] - obtained = utils.extractValuesFlow( example ) + obtained = lrf.extractValuesFlow( example ) self.assertEqual( expected, obtained ) example = ( "compflowStatistics, Reservoir: Cell fluid mass" " (min, max): 10765.1, 2.2694e+10 kg" ) expected = [ 10765.1, 2.2694e10 ] - obtained = utils.extractValuesFlow( example ) + obtained = lrf.extractValuesFlow( example ) self.assertEqual( expected, obtained ) example = ( "compflowStatistics, Region1 (time 256800000 s): Pressure" " (min, average, max): 10023287.92961521, 10271543.591259222," " 10525096.98374942 Pa" ) expected = [ 10023287.92961521, 10271543.591259222, 10525096.98374942 ] - obtained = utils.extractValuesFlow( example ) + obtained = lrf.extractValuesFlow( example ) self.assertEqual( expected, obtained ) example = ( "compflowStatistics, Region1 (time 4320000 s): Phase dynamic" " pore volume: [0, 799999924.1499865] rm^3" ) expected = [ 0, 799999924.1499865 ] - obtained = utils.extractValuesFlow( example ) + obtained = lrf.extractValuesFlow( example ) self.assertEqual( expected, obtained ) def test_convertValues( self: Self ) -> None: @@ -213,20 +213,20 @@ def test_convertValues( self: Self ) -> None: propertyValues: list[ float ] = [ 1e6, 2e8 ] propertyUnits: dict[ str, Unit ] = getSIUnits() expected: list[ float ] = [ 1e6, 2e8 ] - obtained: list[ float ] = utils.convertValues( propertyNames, propertyValues, propertyUnits ) + obtained: list[ float ] = lrf.convertValues( propertyNames, propertyValues, propertyUnits ) self.assertEqual( expected, obtained ) propertyNames = [ "WellControls__TotalFluidDensity" ] propertyValues = [ 1e4 ] expected = [ 1e4 ] - obtained = utils.convertValues( propertyNames, propertyValues, propertyUnits ) + obtained = lrf.convertValues( propertyNames, propertyValues, propertyUnits ) self.assertEqual( expected, obtained ) def test_extractWell( self: Self ) -> None: """Test extractWell function.""" line = " TableFunction: well.CO2001_ConstantBHP_table" expected = "well.CO2001" - obtained = utils.extractWell( line ) + obtained = lrf.extractWell( line ) self.assertEqual( expected, obtained ) def test_identifyCurrentWell( self: Self ) -> None: @@ -234,52 +234,52 @@ def test_identifyCurrentWell( self: Self ) -> None: lastWellName: str = "well1" line: str = ( "The total rate is 0 kg/s, which corresponds to a" + "total surface volumetric rate of 0 sm3/s" ) expected: str = "well1" - obtained: str = utils.identifyCurrentWell( line, lastWellName ) + obtained: str = lrf.identifyCurrentWell( line, lastWellName ) self.assertEqual( expected, obtained ) line = ( "Rank 18: well.CO2001: BHP (at the specified reference" + " elevation): 19318538.400682557 Pa" ) expected = "well.CO2001" - obtained = utils.identifyCurrentWell( line, lastWellName ) + obtained = lrf.identifyCurrentWell( line, lastWellName ) self.assertEqual( expected, obtained ) line = ( "wellControls1: BHP (at the specified reference" + " elevation): 12337146.157562563 Pa" ) expected = "wellControls1" - obtained = utils.identifyCurrentWell( line, lastWellName ) + obtained = lrf.identifyCurrentWell( line, lastWellName ) self.assertEqual( expected, obtained ) def test_extractWellTags( self: Self ) -> None: """Test extractWellTags function.""" line: str = ( "Rank 18: well.CO2001: BHP " + "(at the specified reference elevation): 193000 Pa" ) expected: list[ str ] = [ "BHP" ] - obtained: list[ str ] = utils.extractWellTags( line ) + obtained: list[ str ] = lrf.extractWellTags( line ) self.assertEqual( expected, obtained ) line = ( "The total rate is 0 kg/s, which corresponds" + " to a total surface volumetric rate of 0 sm3/s" ) expected = [ "total massRate", "total surface volumetricRate" ] - obtained = utils.extractWellTags( line ) + obtained = lrf.extractWellTags( line ) self.assertEqual( expected, obtained ) def test_extractValuesWell( self: Self ) -> None: """Test extractValuesWell function.""" line: str = ( "Rank 18: well.CO2001: BHP " + "(at the specified reference elevation): 193000 Pa" ) expected: list[ float ] = [ 193000.0 ] - obtained: list[ float ] = utils.extractValuesWell( line, 1 ) + obtained: list[ float ] = lrf.extractValuesWell( line, 1 ) self.assertEqual( expected, obtained ) line = ( "The total rate is 0 kg/s, which corresponds" + " to a total surface volumetric rate of 0 sm3/s" ) expected = [ 0.0, 0.0 ] - obtained = utils.extractValuesWell( line, 2 ) + obtained = lrf.extractValuesWell( line, 2 ) self.assertEqual( expected, obtained ) line = "The phase surface volumetric rate is" + " 1.9466968733035026e-12 sm3/s" expected = [ 1.9466968733035026e-12 ] - obtained = utils.extractValuesWell( line, 1 ) + obtained = lrf.extractValuesWell( line, 1 ) self.assertEqual( expected, obtained ) def test_extractAquifer( self: Self ) -> None: """Test extractAquifer function.""" line: str = " TableFunction:aquifer1_pressureInfluence_table" expected: str = "aquifer1" - obtained: str = utils.extractAquifer( line ) + obtained: str = lrf.extractAquifer( line ) self.assertEqual( expected, obtained ) def test_extractValueAndNameAquifer( self: Self ) -> None: @@ -289,7 +289,7 @@ def test_extractValueAndNameAquifer( self: Self ) -> None: " boundary condition 'aquifer1' produces a flux of" + " -0.6181975187076816 kg (or moles if useMass=0)." ) expected: tuple[ str, float ] = ( "aquifer1", -0.6181975187076816 ) - obtained: tuple[ str, float ] = utils.extractValueAndNameAquifer( line ) + obtained: tuple[ str, float ] = lrf.extractValueAndNameAquifer( line ) self.assertEqual( expected, obtained ) line = ( "FlowSolverBase compositionalMultiphaseFVMSolver" + @@ -298,14 +298,14 @@ def test_extractValueAndNameAquifer( self: Self ) -> None: " boundary condition 'Aquifer3' produces a flux of" + " -0.8441759009606705 kg (or moles if useMass=0). " ) expected = ( "Aquifer3", -0.8441759009606705 ) - obtained = utils.extractValueAndNameAquifer( line ) + obtained = lrf.extractValueAndNameAquifer( line ) self.assertEqual( expected, obtained ) def test_extractNewtonIter( self: Self ) -> None: """Test extractNewtonIter function.""" line: str = " Attempt: 2, ConfigurationIter: 1, NewtonIter: 8" expected: int = 8 - obtained: int = utils.extractNewtonIter( line ) + obtained: int = lrf.extractNewtonIter( line ) self.assertEqual( expected, obtained ) def test_extractLinearIter( self: Self ) -> None: @@ -314,7 +314,7 @@ def test_extractLinearIter( self: Self ) -> None: " 5.96636e-05 | Make Restrictor Time: 0 | Compute Auu Time: 0 |" + " SC Filter Time: 0 | Setup Time: 1.5156 s | Solve Time:" + " 0.041093 s" ) expected: int = 23 - obtained: int = utils.extractLinearIter( line ) + obtained: int = lrf.extractLinearIter( line ) self.assertEqual( expected, obtained ) def test_timeInSecond( self: Self ) -> None: @@ -327,56 +327,56 @@ def test_timeInSecond( self: Self ) -> None: "s": 0, } expected: float = 0.0 - obtained: float = utils.timeInSecond( timeCounter ) + obtained: float = lrf.timeInSecond( timeCounter ) self.assertEqual( expected, obtained ) timeCounter = { "years": 1, "days": 1, "hrs": 1, "min": 1, "s": 1 } expected = 31647661.0 - obtained = utils.timeInSecond( timeCounter ) + obtained = lrf.timeInSecond( timeCounter ) self.assertEqual( expected, obtained ) def test_extractTimeAndDt( self: Self ) -> None: """Test extractTimeAndDt function.""" line: str = "Time: 1 s, dt: 1 s, Cycle: 0" expected: tuple[ float, float ] = ( 1.0, 1.0 ) - obtained: tuple[ float, float ] = utils.extractTimeAndDt( line ) + obtained: tuple[ float, float ] = lrf.extractTimeAndDt( line ) self.assertEqual( expected, obtained ) line = "Time: 1s, dt: 1s, Cycle: 0" expected = ( 1.0, 1.0 ) - obtained = utils.extractTimeAndDt( line ) + obtained = lrf.extractTimeAndDt( line ) self.assertEqual( expected, obtained ) line = "Time: 1e5s, dt: 1e6s, Cycle: 0" expected = ( 1.0e5, 1.0e6 ) - obtained = utils.extractTimeAndDt( line ) + obtained = lrf.extractTimeAndDt( line ) self.assertEqual( expected, obtained ) line = "Time: 1 min, dt: 1 s, Cycle: 0" expected = ( 60.0, 1.0 ) - obtained = utils.extractTimeAndDt( line ) + obtained = lrf.extractTimeAndDt( line ) self.assertEqual( expected, obtained ) line = "Time: 1 hrs, dt: 1 s, Cycle: 0" expected = ( 3600.0, 1.0 ) - obtained = utils.extractTimeAndDt( line ) + obtained = lrf.extractTimeAndDt( line ) self.assertEqual( expected, obtained ) line = "Time: 1 days, dt: 1 s, Cycle: 0" expected = ( 86400.0, 1.0 ) - obtained = utils.extractTimeAndDt( line ) + obtained = lrf.extractTimeAndDt( line ) self.assertEqual( expected, obtained ) line = "Time: 1 years, 1 days, 1 hrs, 1 min, 1 s, dt: 1 s, Cycle: 1" expected = ( 31647661.0, 1.0 ) - obtained = utils.extractTimeAndDt( line ) + obtained = lrf.extractTimeAndDt( line ) self.assertEqual( expected, obtained ) def test_identifyProperties( self: Self ) -> None: """Test identifyProperties function.""" properties: list[ str ] = [ "WellControls_TotalFluidDensity" ] expected: list[ str ] = [ "35:WellControls_TotalFluidDensity" ] - obtained: list[ str ] = utils.identifyProperties( properties ) + obtained: list[ str ] = lrf.identifyProperties( properties ) self.assertEqual( expected, obtained ) def test_findNumberPhasesSimulation( self: Self ) -> None: @@ -385,26 +385,26 @@ def test_findNumberPhasesSimulation( self: Self ) -> None: pathToFile: str = os.path.join( dir_path, "Data/" ) filepath: str = os.path.join( pathToFile, filename ) expected: int = 2 - obtained: int = utils.findNumberPhasesSimulation( filepath ) + obtained: int = lrf.findNumberPhasesSimulation( filepath ) self.assertEqual( expected, obtained ) def test_transformUserChoiceToListPhases( self: Self ) -> None: """Test phaseNameBuilder function with 3 phases.""" userChoice: str = "phase0 phase1 phase2" expected: list[ str ] = [ "phase0", "phase1", "phase2" ] - obtained: list[ str ] = utils.transformUserChoiceToListPhases( userChoice ) + obtained: list[ str ] = lrf.transformUserChoiceToListPhases( userChoice ) self.assertEqual( expected, obtained ) userChoice = "phase0, phase1, phase2" expected = [ "phase0", "phase1", "phase2" ] - obtained = utils.transformUserChoiceToListPhases( userChoice ) + obtained = lrf.transformUserChoiceToListPhases( userChoice ) self.assertEqual( expected, obtained ) userChoice = "phase0; phase1; phase2" expected = [] capturedOutput = io.StringIO() with contextlib.redirect_stdout( capturedOutput ): - obtained = utils.transformUserChoiceToListPhases( userChoice ) + obtained = lrf.transformUserChoiceToListPhases( userChoice ) self.assertEqual( expected, obtained ) self.assertGreater( len( capturedOutput.getvalue() ), 0 ) @@ -412,17 +412,17 @@ def test_phaseNamesBuilder( self: Self ) -> None: """Test phaseNameBuilder function with 4 phases.""" phasesFromUser: list[ str ] = [] expected: list[ str ] = [ "phase0", "phase1", "phase2", "phase3" ] - obtained: list[ str ] = utils.phaseNamesBuilder( 4, phasesFromUser ) + obtained: list[ str ] = lrf.phaseNamesBuilder( 4, phasesFromUser ) self.assertEqual( expected, obtained ) phasesFromUser = [ "water", "gas" ] expected = [ "water", "gas", "phase2", "phase3" ] - obtained = utils.phaseNamesBuilder( 4, phasesFromUser ) + obtained = lrf.phaseNamesBuilder( 4, phasesFromUser ) self.assertEqual( expected, obtained ) phasesFromUser = [ "water", "CO2", "N2", "H2", "CH4" ] expected = [ "water", "CO2", "N2", "H2" ] - obtained = utils.phaseNamesBuilder( 4, phasesFromUser ) + obtained = lrf.phaseNamesBuilder( 4, phasesFromUser ) self.assertEqual( expected, obtained ) # TODO def test_extractValuesFromBlockWhenMultipleComponents(self :Self) diff --git a/geos-pv/tests/testsGeosLogReaderConvergence.py b/geos-pv/tests/testsGeosLogReaderConvergence.py index 1c2e2897..c41d2ab9 100644 --- a/geos-pv/tests/testsGeosLogReaderConvergence.py +++ b/geos-pv/tests/testsGeosLogReaderConvergence.py @@ -14,7 +14,7 @@ if parent_dir_path not in sys.path: sys.path.append( parent_dir_path ) -from geos_posp.readers.GeosLogReaderConvergence import GeosLogReaderConvergence +from geos.pv.geosLogReaderUtils.GeosLogReaderConvergence import GeosLogReaderConvergence from geos.utils.UnitRepository import Unit, UnitRepository unitsObjSI: UnitRepository = UnitRepository() diff --git a/geos-pv/tests/testsGeosLogReaderFlow.py b/geos-pv/tests/testsGeosLogReaderFlow.py index 2c9b0ba8..977a1558 100644 --- a/geos-pv/tests/testsGeosLogReaderFlow.py +++ b/geos-pv/tests/testsGeosLogReaderFlow.py @@ -15,7 +15,7 @@ sys.path.append( parent_dir_path ) from geos.utils.UnitRepository import Unit, UnitRepository -from geos_posp.readers.GeosLogReaderFlow import GeosLogReaderFlow +from geos.pv.geosLogReaderUtils.GeosLogReaderFlow import GeosLogReaderFlow unitsObjSI: UnitRepository = UnitRepository() conversionFactors: dict[ str, Unit ] = unitsObjSI.getPropertiesUnit() diff --git a/geos-pv/tests/testsGeosLogReaderWells.py b/geos-pv/tests/testsGeosLogReaderWells.py index ee2b21d7..8d7fe719 100644 --- a/geos-pv/tests/testsGeosLogReaderWells.py +++ b/geos-pv/tests/testsGeosLogReaderWells.py @@ -17,7 +17,7 @@ import pandas as pd # type: ignore[import-untyped] -from geos_posp.readers.GeosLogReaderWells import GeosLogReaderWells +from geos.pv.geosLogReaderUtils.GeosLogReaderWells import GeosLogReaderWells from geos.utils.UnitRepository import Unit, UnitRepository unitsObjSI = UnitRepository() diff --git a/geos-pv/tests/testsInvalidLogs.py b/geos-pv/tests/testsInvalidLogs.py index fb9cce2c..c09e4169 100644 --- a/geos-pv/tests/testsInvalidLogs.py +++ b/geos-pv/tests/testsInvalidLogs.py @@ -15,10 +15,10 @@ if parent_dir_path not in sys.path: sys.path.append( parent_dir_path ) -from geos_posp.readers.GeosLogReaderAquifers import GeosLogReaderAquifers -from geos_posp.readers.GeosLogReaderConvergence import GeosLogReaderConvergence -from geos_posp.readers.GeosLogReaderFlow import GeosLogReaderFlow -from geos_posp.readers.GeosLogReaderWells import GeosLogReaderWells +from geos.pv.geosLogReaderUtils.GeosLogReaderAquifers import GeosLogReaderAquifers +from geos.pv.geosLogReaderUtils.GeosLogReaderConvergence import GeosLogReaderConvergence +from geos.pv.geosLogReaderUtils.GeosLogReaderFlow import GeosLogReaderFlow +from geos.pv.geosLogReaderUtils.GeosLogReaderWells import GeosLogReaderWells from geos.utils.UnitRepository import Unit, UnitRepository unitsObjSI: UnitRepository = UnitRepository()