From 6ca7d21d56eeca25192ec8e24f02ab833816a004 Mon Sep 17 00:00:00 2001 From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:54:16 +0100 Subject: [PATCH 1/6] DBG --- docs/geos_posp_docs/PVplugins.rst | 4 --- docs/geos_pv_docs/processing.rst | 6 ++++ docs/geos_pv_docs/pyplotUtils.rst | 2 +- .../src/geos/pv/plugins}/PVMohrCirclePlot.py | 33 ++++++++++++------- .../geos/pv/utils}/mohrCircles/__init__.py | 0 .../utils}/mohrCircles/functionsMohrCircle.py | 0 .../pv/utils}/mohrCircles/mainMohrCircles.py | 0 .../pv/utils}/mohrCircles/plotMohrCircles.py | 0 8 files changed, 28 insertions(+), 17 deletions(-) rename {geos-posp/src/PVplugins => geos-pv/src/geos/pv/plugins}/PVMohrCirclePlot.py (96%) rename {geos-posp/src/geos_posp/visu => geos-pv/src/geos/pv/utils}/mohrCircles/__init__.py (100%) rename {geos-posp/src/geos_posp/visu => geos-pv/src/geos/pv/utils}/mohrCircles/functionsMohrCircle.py (100%) rename {geos-posp/src/geos_posp/visu => geos-pv/src/geos/pv/utils}/mohrCircles/mainMohrCircles.py (100%) rename {geos-posp/src/geos_posp/visu => geos-pv/src/geos/pv/utils}/mohrCircles/plotMohrCircles.py (100%) diff --git a/docs/geos_posp_docs/PVplugins.rst b/docs/geos_posp_docs/PVplugins.rst index 8f41728b..63a1e91e 100644 --- a/docs/geos_posp_docs/PVplugins.rst +++ b/docs/geos_posp_docs/PVplugins.rst @@ -58,10 +58,6 @@ PVGeomechanicsWorkflowVolumeWell plugin .. automodule:: PVplugins.PVGeomechanicsWorkflowVolumeWell -PVMohrCirclePlot plugin ---------------------------------- - -.. automodule:: PVplugins.PVMohrCirclePlot PVplugins.PVSurfaceGeomechanics module -------------------------------------- diff --git a/docs/geos_pv_docs/processing.rst b/docs/geos_pv_docs/processing.rst index 7b3753b6..9a321d98 100644 --- a/docs/geos_pv_docs/processing.rst +++ b/docs/geos_pv_docs/processing.rst @@ -25,6 +25,12 @@ PVGeomechanicsCalculator plugin .. automodule:: geos.pv.plugins.PVGeomechanicsCalculator +PVMohrCirclePlot plugin +--------------------------------- + +.. automodule:: PVplugins.PVMohrCirclePlot + + PVSplitMesh ---------------------------------- 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/geos-posp/src/PVplugins/PVMohrCirclePlot.py b/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py similarity index 96% rename from geos-posp/src/PVplugins/PVMohrCirclePlot.py rename to geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py index 5a04106c..b8f23800 100644 --- a/geos-posp/src/PVplugins/PVMohrCirclePlot.py +++ b/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py @@ -2,8 +2,8 @@ # 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 os import sys +from pathlib import Path from enum import Enum from typing import Any, Union, cast @@ -22,15 +22,14 @@ 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 + +update_paths() -import PVplugins # noqa: F401 -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 ( @@ -45,11 +44,20 @@ ) 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_posp.visu.mohrCircles.functionsMohrCircle as mcf +import geos.pv.utils.mohrCircles.functionsMohrCircle as mcf +# import geos_posp.visu.PVUtils.paraviewTreatments as pvt +import geos.pv.utils.paraviewTreatments as pvt +from geos.pv.utils.checkboxFunction import ( # type: ignore[attr-defined] +# from geos_posp.visu.PVUtils.checkboxFunction import ( # type: ignore[attr-defined] createModifiedCallback, ) -from geos_posp.visu.PVUtils.DisplayOrganizationParaview import ( +# from geos_posp.visu.PVUtils.DisplayOrganizationParaview import ( +from geos.pv.utils.DisplayOrganizationParaview import ( buildNewLayoutWithPythonView, ) -from geos_posp.visu.PVUtils.matplotlibOptions import ( +# from geos_posp.visu.PVUtils.matplotlibOptions import ( +from geos.pv.pyplotUtils.matplotlibOptions import ( FontStyleEnum, FontWeightEnum, LegendLocationEnum, @@ -84,7 +92,8 @@ @smproxy.filter( name="PVMohrCirclePlot", label="Plot Mohr's Circles" ) @smhint.xml( """ - + # + """ ) @smproperty.input( name="Input", port_index=0 ) diff --git a/geos-posp/src/geos_posp/visu/mohrCircles/__init__.py b/geos-pv/src/geos/pv/utils/mohrCircles/__init__.py similarity index 100% rename from geos-posp/src/geos_posp/visu/mohrCircles/__init__.py rename to geos-pv/src/geos/pv/utils/mohrCircles/__init__.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 100% rename from geos-posp/src/geos_posp/visu/mohrCircles/functionsMohrCircle.py rename to geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py diff --git a/geos-posp/src/geos_posp/visu/mohrCircles/mainMohrCircles.py b/geos-pv/src/geos/pv/utils/mohrCircles/mainMohrCircles.py similarity index 100% rename from geos-posp/src/geos_posp/visu/mohrCircles/mainMohrCircles.py rename to geos-pv/src/geos/pv/utils/mohrCircles/mainMohrCircles.py diff --git a/geos-posp/src/geos_posp/visu/mohrCircles/plotMohrCircles.py b/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py similarity index 100% rename from geos-posp/src/geos_posp/visu/mohrCircles/plotMohrCircles.py rename to geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py From 1e0c1d95c9ced04c1d8b5942cff2ebd5419d7c83 Mon Sep 17 00:00:00 2001 From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:50:32 +0100 Subject: [PATCH 2/6] Fix import after moving files --- docs/geos_posp_docs/visu.PVUtils.rst | 37 ------ docs/geos_posp_docs/visualization.rst | 9 -- .../mohrCircles.rst} | 8 +- docs/geos_pv_docs/utilities.rst | 2 + geos-pv/examples/PVproxyWidgetsDynamic.py | 2 +- .../utils/mohrCircles/functionsMohrCircle.py | 2 +- .../pv/utils/mohrCircles/mainMohrCircles.py | 4 +- .../pv/utils/mohrCircles/plotMohrCircles.py | 4 +- geos-pv/tests/testsFunctionsGeosLogReader.py | 116 +++++++++--------- .../tests/testsGeosLogReaderConvergence.py | 2 +- geos-pv/tests/testsGeosLogReaderFlow.py | 2 +- geos-pv/tests/testsGeosLogReaderWells.py | 2 +- geos-pv/tests/testsInvalidLogs.py | 8 +- 13 files changed, 77 insertions(+), 121 deletions(-) delete mode 100644 docs/geos_posp_docs/visu.PVUtils.rst delete mode 100644 docs/geos_posp_docs/visualization.rst rename docs/{geos_posp_docs/visu.mohrCircles.rst => geos_pv_docs/mohrCircles.rst} (62%) 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/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/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-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-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py b/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py index 0d4042e3..a4f56481 100644 --- a/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py @@ -9,7 +9,7 @@ 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, ) diff --git a/geos-pv/src/geos/pv/utils/mohrCircles/mainMohrCircles.py b/geos-pv/src/geos/pv/utils/mohrCircles/mainMohrCircles.py index 7eb27a13..8b341d7f 100644 --- a/geos-pv/src/geos/pv/utils/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() diff --git a/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py b/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py index d7c21c6c..37ecf022 100644 --- a/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py @@ -15,8 +15,8 @@ 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, 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() From d178597ccdbc13e0b617853d9e9bb7c46aded3e0 Mon Sep 17 00:00:00 2001 From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:05:48 +0100 Subject: [PATCH 3/6] fix path --- docs/geos-posp.rst | 4 +- docs/geos_posp_docs/visu.rst | 12 -- .../src/geos/pv/plugins/PVMohrCirclePlot.py | 182 +++++++++--------- .../src/geos/pv/utils/mohrCircles/__init__.py | 2 +- 4 files changed, 90 insertions(+), 110 deletions(-) delete mode 100644 docs/geos_posp_docs/visu.rst 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/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/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py b/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py index b8f23800..bf375340 100644 --- a/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py +++ b/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py @@ -46,17 +46,12 @@ from geos.mesh.utils.multiblockModifiers import mergeBlocks -# import geos_posp.visu.mohrCircles.functionsMohrCircle as mcf import geos.pv.utils.mohrCircles.functionsMohrCircle as mcf -# import geos_posp.visu.PVUtils.paraviewTreatments as pvt import geos.pv.utils.paraviewTreatments as pvt from geos.pv.utils.checkboxFunction import ( # type: ignore[attr-defined] -# from geos_posp.visu.PVUtils.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, @@ -92,8 +87,7 @@ @smproxy.filter( name="PVMohrCirclePlot", label="Plot Mohr's Circles" ) @smhint.xml( """ - # - + """ ) @smproperty.input( name="Input", port_index=0 ) @@ -111,42 +105,42 @@ def __init__( self: Self ) -> None: super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkDataObject" ) # create a new PythonView - self.m_pythonView: Any = buildNewLayoutWithPythonView() + self.pythonView: Any = buildNewLayoutWithPythonView() #: list of all cell ids - self.m_cellIds: list[ str ] = [] + self.cellIds: list[ str ] = [] #: cell selection object - self.m_cellIdsDAS: vtkDAS = vtkDAS() - self.m_cellIdsDAS.AddObserver( 0, createModifiedCallback( self ) ) + self.cellIdsDAS: vtkDAS = vtkDAS() + self.cellIdsDAS.AddObserver( 0, createModifiedCallback( self ) ) #: list of all time steps - self.m_timeSteps: npt.NDArray[ np.float64 ] = np.array( [] ) + self.timeSteps: npt.NDArray[ np.float64 ] = np.array( [] ) #: time steps selection object - self.m_timeStepsDAS: vtkDAS = vtkDAS() - self.m_timeStepsDAS.AddObserver( 0, createModifiedCallback( self ) ) + self.timeStepsDAS: vtkDAS = vtkDAS() + self.timeStepsDAS.AddObserver( 0, createModifiedCallback( self ) ) #: list of all mohr circles - self.m_mohrCircles: list[ MohrCircle ] = [] + self.mohrCircles: list[ MohrCircle ] = [] #: failure envelop parameters - self.m_rockCohesion: float = DEFAULT_ROCK_COHESION - self.m_frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD + self.rockCohesion: float = DEFAULT_ROCK_COHESION + self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD #: stress convention (False for GEOS convention) - self.m_stressConvention: bool = False + self.stressConvention: bool = False #: 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 + 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 ] = { + self.userChoices: dict[ str, Any ] = { "xAxis": "Normal stress", "yAxis": "Shear stress", "stressUnit": 0, @@ -169,10 +163,10 @@ def __init__( self: Self ) -> None: } #: request data processing step - incremented each time RequestUpdateExtent is called - self.m_requestDataStep: int = -1 + self.requestDataStep: int = -1 #: logger - self.m_logger: Logger = getLogger( "Mohr's Circle Analysis Filter" ) + self.logger: Logger = getLogger( "Mohr's Circle Analysis Filter" ) def getUserChoices( self: Self ) -> dict[ str, Any ]: """Access the m_userChoices attribute. @@ -180,7 +174,7 @@ def getUserChoices( self: Self ) -> dict[ str, Any ]: Returns: dict[str] : the user choices for the figure. """ - return self.m_userChoices + return self.userChoices def getCircleIds( self: Self ) -> list[ str ]: """Get circle ids to plot. @@ -194,12 +188,12 @@ def getCircleIds( self: Self ) -> list[ str ]: 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.userChoices[ "curvesAspect" ][ self.circleIdUsed ] = { + "color": self.color, + "linestyle": self.lineStyle, + "linewidth": self.lineWidth, + "marker": self.markerStyle, + "markersize": self.markerSize, } @smproperty.xml( """ @@ -212,8 +206,8 @@ def defineCurvesAspect( self: Self ) -> None: """ ) 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.Modified() @smproperty.dataarrayselection( name="CellIdToPlot" ) @@ -223,7 +217,7 @@ def a01GetCellIdsDAS( self: Self ) -> vtkDAS: Returns: vtkDataArraySelection: selected cell ids. """ - return self.m_cellIdsDAS + return self.cellIdsDAS @smproperty.dataarrayselection( name="TimeStepsToPlot" ) def a02GetTimestepsToPlot( self: Self ) -> vtkDAS: @@ -232,7 +226,7 @@ def a02GetTimestepsToPlot( self: Self ) -> vtkDAS: Returns: vtkDataArraySelection: selected time steps. """ - return self.m_timeStepsDAS + return self.timeStepsDAS @smproperty.xml( """ @@ -254,7 +248,7 @@ def b01SetCohesion( self: Self, value: float ) -> None: Args: value (float): rock cohesion (Pa) """ - self.m_rockCohesion = value + self.rockCohesion = value self.Modified() @smproperty.doublevector( @@ -268,7 +262,7 @@ def b02SetFrictionAngle( self: Self, value: float ) -> None: Args: value (float): friction angle (°). """ - self.m_frictionAngle = value * np.pi / 180.0 + self.frictionAngle = value * np.pi / 180.0 self.Modified() @smproperty.xml( """ None: Args: choice (int): stress unit index in Pressure enum. """ - self.m_userChoices[ "stressUnit" ] = choice + self.userChoices[ "stressUnit" ] = choice self.Modified() @smproperty.intvector( @@ -305,7 +299,7 @@ def b05SetStressCompressionConvention( self: Self, boolean: bool ) -> None: geomechanical convention. """ # need to convert Geos results if use the usual convention - self.m_stressConvention = boolean + self.stressConvention = boolean self.Modified() @smproperty.intvector( name="AnnotateCircles", label="Annotate Circles", default_values=1 ) @@ -316,7 +310,7 @@ def b06SetAnnotateCircles( self: Self, boolean: bool ) -> None: Args: boolean (bool): user choce. """ - self.m_userChoices[ "annotateCircles" ] = boolean + self.userChoices[ "annotateCircles" ] = boolean self.Modified() @smproperty.intvector( name="Minorticks", label="Minorticks", default_values=0 ) @@ -327,7 +321,7 @@ def b07SetMinorticks( self: Self, boolean: bool ) -> None: Args: boolean (bool): user choice. """ - self.m_userChoices[ "minorticks" ] = boolean + self.userChoices[ "minorticks" ] = boolean self.Modified() @smproperty.xml( """ None: Args: boolean (bool): user choice. """ - 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: @@ -359,7 +353,7 @@ def c01SetTitlePlot( self: Self, title: str ) -> None: Args: title (str): title. """ - self.m_userChoices[ "title" ] = title + self.userChoices[ "title" ] = title self.Modified() @smproperty.intvector( name="Title Style", label="Title Style", default_values=0 ) @@ -371,7 +365,7 @@ def c02SetTitleStyle( self: Self, value: int ) -> None: 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 ) @@ -383,7 +377,7 @@ def c03SetTitleWeight( self: Self, value: int ) -> None: 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 ) @@ -394,7 +388,7 @@ def c04SetTitleSize( self: Self, size: float ) -> None: Args: size (float): title font size between 1 and 50. """ - self.m_userChoices[ "titleSize" ] = size + self.userChoices[ "titleSize" ] = size self.Modified() @smproperty.xml( """ @@ -419,7 +413,7 @@ def d01SetLegendPosition( self: Self, value: int ) -> None: 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 ) @@ -430,7 +424,7 @@ def d02SetLegendSize( self: Self, size: float ) -> None: Args: size (float): legend font size between 1 and 50. """ - self.m_userChoices[ "legendSize" ] = size + self.userChoices[ "legendSize" ] = size self.Modified() @smproperty.xml( """ @@ -452,7 +446,7 @@ def e01SetCustomAxisLim( self: Self, boolean: bool ) -> None: Args: boolean (bool): user choice. """ - self.m_userChoices[ "customAxisLim" ] = boolean + self.userChoices[ "customAxisLim" ] = boolean self.Modified() @smproperty.doublevector( name="LimMinX", label="X min", default_values=-1e36 ) @@ -465,7 +459,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 ) @@ -478,7 +472,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 ) @@ -491,7 +485,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 ) @@ -504,7 +498,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: Args: boolean (bool): user choice. """ - self.m_modifyCurvesAspect = boolean + self.modifyCurvesAspect = boolean @smproperty.stringvector( name="CurvesInfo", information_only="1" ) def f02GetCurveNames( self: Self ) -> list[ str ]: @@ -552,7 +546,7 @@ def f03SetCellID( self: Self, value: str ) -> None: Args: value (str): circle ids. """ - self.m_circleIdUsed = value + self.circleIdUsed = value self.Modified() @smproperty.intvector( name="LineStyle", label="Line Style", default_values=1 ) @@ -564,7 +558,7 @@ def f04SetLineStyle( self: Self, value: int ) -> None: 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 ) @@ -575,7 +569,7 @@ def f05SetLineWidth( self: Self, value: float ) -> None: Args: 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 ) @@ -587,7 +581,7 @@ def f06SetMarkerStyle( self: Self, value: int ) -> None: 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 ) @@ -598,7 +592,7 @@ def f07SetMarkerSize( self: Self, value: float ) -> None: Args: value (float): size of markers between 1 and 30. """ - self.m_markerSize = value + self.markerSize = value self.Modified() @smproperty.xml( """ @@ -626,7 +620,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 ]: """Filter the list of all MohrCircle to get those to plot. @@ -829,4 +823,4 @@ def filterMohrCircles( self: Self ) -> list[ MohrCircle ]: """ # circle ids to plot circleIds: list[ str ] = self.getCircleIds() - return [ mohrCircle for mohrCircle in self.m_mohrCircles if mohrCircle.getCircleId() in circleIds ] + return [ mohrCircle for mohrCircle in self.mohrCircles if mohrCircle.getCircleId() in circleIds ] diff --git a/geos-pv/src/geos/pv/utils/mohrCircles/__init__.py b/geos-pv/src/geos/pv/utils/mohrCircles/__init__.py index 2aa543df..761b9894 100644 --- a/geos-pv/src/geos/pv/utils/mohrCircles/__init__.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/__init__.py @@ -1,2 +1,2 @@ -MOHR_CIRCLE_PATH: str = "geos_posp/visu/mohrCircles/" +MOHR_CIRCLE_PATH: str = "src/geos/pv/utils/mohrCircles/" MOHR_CIRCLE_ANALYSIS_MAIN = "mainMohrCircles.py" From 771f1e6798ed087f5de431fa1f28d160e7af52a8 Mon Sep 17 00:00:00 2001 From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:31:06 +0100 Subject: [PATCH 4/6] Removing systematic calculation and stack of all timesteps and cells --- .../src/geos/pv/plugins/PVMohrCirclePlot.py | 166 ++++++++++++------ .../utils/mohrCircles/functionsMohrCircle.py | 25 +-- .../src/geos/pv/utils/paraviewTreatments.py | 32 +--- 3 files changed, 132 insertions(+), 91 deletions(-) diff --git a/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py b/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py index bf375340..7db0adfa 100644 --- a/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py +++ b/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py @@ -3,6 +3,7 @@ # SPDX-FileContributor: Alexandre Benedicto, Martin Lemay # ruff: noqa: E402 # disable Module level import not at top of file import sys +import logging from pathlib import Path from enum import Enum from typing import Any, Union, cast @@ -14,11 +15,13 @@ 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, ) @@ -36,14 +39,14 @@ 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.mesh.utils.multiblockModifiers import mergeBlocks import geos.pv.utils.mohrCircles.functionsMohrCircle as mcf @@ -61,6 +64,7 @@ OptionSelectionEnum, optionEnumToXml, ) +from geos.pv.utils.mohrCircles.functionsMohrCircle import StressConventionEnum __doc__ = """ PVMohrCirclePlot is a Paraview plugin that allows to compute and plot @@ -73,15 +77,28 @@ 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. + +Please be aware that the number of cells and timesteps should be limited, or Paraview may crash due to overload. + +* 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. -.. 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. +* 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:: + After first application of the Mohr circle filter, display properties, such as line colors, title, ticks, ... However the time steps and cell Ids cannot be calculated a second time. """ @@ -92,7 +109,7 @@ """ ) @smproperty.input( name="Input", port_index=0 ) @smdomain.datatype( - dataTypes=[ "vtkUnstructuredGrid", "vtkMultiBlockDataSet" ], + dataTypes=[ "vtkUnstructuredGrid" ], composite_data_supported=False, ) class PVMohrCirclePlot( VTKPythonAlgorithmBase ): @@ -128,8 +145,8 @@ def __init__( self: Self ) -> None: self.rockCohesion: float = DEFAULT_ROCK_COHESION self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD - #: stress convention (False for GEOS convention) - self.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.circleIdUsed: str = "" @@ -162,13 +179,23 @@ def __init__( self: Self ) -> None: "limMaxY": None, } + self.requestedCellIds: list[ str ] = [] + self.requestedTimeStepsIndexes: list[ int ] = [] + #: request data processing step - incremented each time RequestUpdateExtent is called self.requestDataStep: int = -1 #: logger - self.logger: Logger = getLogger( "Mohr's Circle Analysis Filter" ) - def getUserChoices( self: Self ) -> dict[ str, Any ]: + self.logger = logging.getLogger( "MohrCircle" ) + self.logger.setLevel( logging.INFO ) + if not self.logger.hasHandlers(): + handler = VTKHandler() + handler.setFormatter( CustomLoggerFormatter( False ) ) + + self.logger.addHandler( handler ) + + def _getUserChoices( self: Self ) -> dict[ str, Any ]: """Access the m_userChoices attribute. Returns: @@ -176,7 +203,7 @@ def getUserChoices( self: Self ) -> dict[ str, Any ]: """ return self.userChoices - def getCircleIds( self: Self ) -> list[ str ]: + def _getCircleIds( self: Self ) -> list[ str ]: """Get circle ids to plot. Returns: @@ -184,9 +211,10 @@ def getCircleIds( self: Self ) -> list[ str ]: """ 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: + def _defineCurvesAspect( self: Self ) -> None: """Add curve aspect parameters according to user choices.""" self.userChoices[ "curvesAspect" ][ self.circleIdUsed ] = { "color": self.color, @@ -287,19 +315,19 @@ def b04SetStressUnit( self: Self, choice: int ) -> None: @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 + useGeosConvention (bool): True is Geos convention, False is usual geomechanical convention. """ - # need to convert Geos results if use the usual convention - self.stressConvention = boolean + # Specify if data is from GEOS + self.useGeosStressConvention = useGeosConvention self.Modified() @smproperty.intvector( name="AnnotateCircles", label="Annotate Circles", default_values=1 ) @@ -531,7 +559,7 @@ def f02GetCurveNames( self: Self ) -> list[ str ]: Returns: 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" ) @@ -652,18 +680,21 @@ def RequestInformation( """ executive = self.GetExecutive() # noqa: F841 inInfo = inInfoVec[ 0 ] - # only at initialization step, no change later + + # Only at initialization step, no change later if self.requestDataStep < 0: - # get cell ids + # Get cell ids inData = self.GetInputData( inInfoVec, 0, 0 ) self.cellIds = pvt.getVtkOriginalCellIds( inData ) - # update vtkDAS + # Update vtkDAS for circleId in self.cellIds: if not self.cellIdsDAS.ArrayExists( circleId ): self.cellIdsDAS.AddArray( circleId ) - self.timeSteps = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() ) # type: ignore + # Get the possible timesteps of execution + self.timeSteps: float = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() ) # type: ignore + for timestep in self.timeSteps: if not self.timeStepsDAS.ArrayExists( str( timestep ) ): self.timeStepsDAS.AddArray( str( timestep ) ) @@ -685,7 +716,6 @@ def RequestUpdateExtent( Returns: int: 1 if calculation successfully ended, 0 otherwise. """ - self.logger.info( f"Apply filter {__name__}" ) executive = self.GetExecutive() inInfo = inInfoVec[ 0 ] @@ -694,8 +724,10 @@ def RequestUpdateExtent( # update requestDataStep self.requestDataStep += 1 - # update time according to requestDataStep iterator + if self.requestDataStep == 0: + self._defineRequestedTimeSteps() + if self.requestDataStep < len( self.timeSteps ): inInfo.GetInformationObject( 0 ).Set( executive.UPDATE_TIME_STEP(), # type: ignore[no-any-return] @@ -710,6 +742,7 @@ def RequestUpdateExtent( self.Modified() return 1 + def RequestDataObject( self: Self, request: vtkInformation, @@ -728,12 +761,14 @@ def RequestDataObject( """ inData = self.GetInputData( inInfoVec, 0, 0 ) outData = self.GetOutputData( outInfoVec, 0 ) + assert inData is not None if ( outData is None ) or ( not outData.IsA( inData.GetClassName() ) ): outData = inData.NewInstance() outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] + def RequestData( self: Self, request: vtkInformation, # noqa: F841 @@ -743,41 +778,45 @@ def RequestData( """Inherited from VTKPythonAlgorithmBase::RequestData. Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects Returns: int: 1 if calculation successfully ended, 0 otherwise. """ try: - input: Union[ vtkUnstructuredGrid, vtkMultiBlockDataSet ] = self.GetInputData( inInfoVec, 0, 0 ) + # input: Union[ vtkUnstructuredGrid, vtkMultiBlockDataSet ] = self.GetInputData( inInfoVec, 0, 0 ) + input: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) assert input is not None, "Input data is undefined" executive = self.GetExecutive() - # get mohr circles from all time steps + if self.requestDataStep < len( self.timeSteps ): request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore[no-any-return] currentTimeStep: float = ( inInfoVec[ 0 ].GetInformationObject( 0 ).Get( executive.UPDATE_TIME_STEP() ) # type: ignore[no-any-return] ) - self.mohrCircles.extend( self.createMohrCirclesAtTimeStep( input, currentTimeStep ) ) - # plot mohr circles + if self.requestDataStep in self.requestedTimeStepsIndexes: + self.mohrCircles.extend( self._createMohrCirclesAtTimeStep( input, currentTimeStep ) ) + + # Plot mohr circles else: - # displayed time step, no need to go further + # Displayed time step, no need to go further request.Remove( executive.CONTINUE_EXECUTING() ) # type: ignore[no-any-return] assert self.pythonView is not None, "No Python View was found." - self.defineCurvesAspect() - mohrCircles: list[ MohrCircle ] = self.filterMohrCircles() + self._defineCurvesAspect() + # mohrCircles: list[ MohrCircle ] = self._filterMohrCircles() + self.pythonView.Script = mcf.buildPythonViewScript( geos_pv_path, - mohrCircles, + self.mohrCircles, self.rockCohesion, self.frictionAngle, - self.getUserChoices(), + self._getUserChoices(), ) Render() @@ -788,13 +827,14 @@ def RequestData( except Exception as e: self.logger.error( "Mohr circles cannot be plotted due to:" ) - self.logger.error( str( e ) ) + self.logger.error( e ) return 0 return 1 - def createMohrCirclesAtTimeStep( + def _createMohrCirclesAtTimeStep( self: Self, - mesh: Union[ vtkUnstructuredGrid, vtkMultiBlockDataSet ], + # mesh: Union[ vtkUnstructuredGrid, vtkMultiBlockDataSet ], + mesh: vtkUnstructuredGrid, currentTimeStep: float, ) -> list[ MohrCircle ]: """Create mohr circles of all cells at the current time step. @@ -806,21 +846,39 @@ 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.cellIds, str( currentTimeStep ), - self.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._defineRequestedCellIds() - 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() + # Circle ids to plot + circleIds: list[ str ] = self._getCircleIds() return [ mohrCircle for mohrCircle in self.mohrCircles if mohrCircle.getCircleId() in circleIds ] + + + def _defineRequestedTimeSteps( self: Self ): + requestedTimeSteps: list[ str ] = pvt.getArrayChoices( self.a02GetTimestepsToPlot() ) + + requestedTimeStepsIndexes = [ pvt.getTimeStepIndex( float( ts ), self.timeSteps ) for ts in requestedTimeSteps ] + + self.requestedTimeStepsIndexes: list[ int ] = requestedTimeStepsIndexes + + + def _defineRequestedCellIds( self: Self ): + self.requestedCellIds: list[ str ] = pvt.getArrayChoices( self.a01GetCellIdsDAS() ) + diff --git a/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py b/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py index a4f56481..ae53f5c9 100644 --- a/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py @@ -3,7 +3,7 @@ # SPDX-FileContributor: Alexandre Benedicto 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 @@ -19,6 +19,16 @@ 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, @@ -34,14 +44,11 @@ def buildPythonViewScript( Args: dir_path (str): directory path - 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. """ @@ -67,14 +74,13 @@ def findAnnotateTuples( mohrCircle: MohrCircle, ) -> tuple[ str, str, tuple[ flo Args: mohrCircle (MohrCircle): input Mohr's circle - maxTau (float): max shear stress Returns: 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 @@ -101,7 +107,7 @@ def createMohrCircleAtTimeStep( stressArray: npt.NDArray[ np.float64 ], cellIds: list[ str ], timeStep: str, - convention: bool, + convention: StressConventionEnum, ) -> list[ MohrCircle ]: """Create MohrCircle object(s) at a given time step for all cell ids. @@ -121,11 +127,10 @@ def createMohrCircleAtTimeStep( """ assert stressArray.shape[ 1 ] == 6, "Stress vector must be of size 6." mohrCircles: list[ MohrCircle ] = [] - sign: float = 1.0 if convention else -1.0 for i, cellId in enumerate( cellIds ): ide: str = getMohrCircleId( cellId, timeStep ) mohrCircle: MohrCircle = MohrCircle( ide ) - mohrCircle.computePrincipalComponents( stressArray[ i ] * sign ) + mohrCircle.computePrincipalComponents( stressArray[ i ] * convention.value ) mohrCircles.append( mohrCircle ) return mohrCircles diff --git a/geos-pv/src/geos/pv/utils/paraviewTreatments.py b/geos-pv/src/geos/pv/utils/paraviewTreatments.py index 98ad009a..996cf02f 100644 --- a/geos-pv/src/geos/pv/utils/paraviewTreatments.py +++ b/geos-pv/src/geos/pv/utils/paraviewTreatments.py @@ -11,13 +11,6 @@ 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, ) @@ -43,6 +36,7 @@ ComponentNameEnum, GeosMeshOutputsEnum, ) +from geos.mesh.utils.multiblockModifiers import mergeBlocks # valid sources for Python view configurator # TODO: need to be consolidated @@ -485,7 +479,7 @@ def getVtkOriginalCellIds( mesh: Union[ vtkMultiBlockDataSet, vtkCompositeDataSe list[str]: ids of the cells. """ # merge blocks for vtkCompositeDataSet - mesh2: vtkUnstructuredGrid = mergeFilterPV( mesh ) + mesh2: vtkUnstructuredGrid = mergeBlocks( mesh ) attributeName: str = GeosMeshOutputsEnum.VTK_ORIGINAL_CELL_ID.attributeName data: vtkCellData = mesh2.GetCellData() assert data is not None, "Cell Data are undefined." @@ -556,7 +550,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 +558,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 +578,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 ) From 928721782810d2bc09eeba9528bfeef54e3d3c2e Mon Sep 17 00:00:00 2001 From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:44:19 +0100 Subject: [PATCH 5/6] change list into sets --- .../src/geos/geomechanics/model/MohrCircle.py | 101 +++++++++++------- .../utils/mohrCircles/functionsMohrCircle.py | 53 +++++---- .../pv/utils/mohrCircles/mainMohrCircles.py | 2 + .../pv/utils/mohrCircles/plotMohrCircles.py | 101 ++++++++---------- 4 files changed, 134 insertions(+), 123 deletions(-) diff --git a/geos-geomechanics/src/geos/geomechanics/model/MohrCircle.py b/geos-geomechanics/src/geos/geomechanics/model/MohrCircle.py index a917c3de..67299c31 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 ( 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-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py b/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py index ae53f5c9..1e4578b6 100644 --- a/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py @@ -19,17 +19,17 @@ 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 + GEOS_STRESS_CONVENTION = -1.0 COMMON_STRESS_CONVENTION = 1.0 - def buildPythonViewScript( dir_path: str, mohrCircles: list[ MohrCircle ], @@ -73,11 +73,11 @@ 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 + tuple[str, str, tuple[float, float], tuple[float, float]]: Labels and location of labels. """ p3, _, p1 = mohrCircle.getPrincipalComponents() @@ -93,9 +93,8 @@ def getMohrCircleId( cellId: str, timeStep: str ) -> str: """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 @@ -108,30 +107,32 @@ def createMohrCircleAtTimeStep( cellIds: list[ str ], timeStep: str, convention: StressConventionEnum, -) -> list[ MohrCircle ]: +) -> 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 - - timeStep (str): time step + 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. - 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 ] = [] + 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 ] * convention.value ) - mohrCircles.append( mohrCircle ) + mohrCircles.add( mohrCircle ) + return mohrCircles @@ -140,11 +141,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: @@ -158,9 +158,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-pv/src/geos/pv/utils/mohrCircles/mainMohrCircles.py b/geos-pv/src/geos/pv/utils/mohrCircles/mainMohrCircles.py index 8b341d7f..2765cd7d 100644 --- a/geos-pv/src/geos/pv/utils/mohrCircles/mainMohrCircles.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/mainMohrCircles.py @@ -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-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py b/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py index 37ecf022..9b21b5a7 100644 --- a/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py @@ -1,15 +1,11 @@ # 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] @@ -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" ) - # 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: From 3dab3386089be271efe32c778616fec5f822827b Mon Sep 17 00:00:00 2001 From: Paloma Martinez <104762252+paloma-martinez@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:31:57 +0100 Subject: [PATCH 6/6] small corrections --- .../src/geos/geomechanics/model/MohrCircle.py | 2 +- .../src/geos/pv/plugins/PVMohrCirclePlot.py | 260 +++++++++--------- .../src/geos/pv/utils/mohrCircles/__init__.py | 4 + .../utils/mohrCircles/functionsMohrCircle.py | 33 ++- .../pv/utils/mohrCircles/plotMohrCircles.py | 2 +- .../src/geos/pv/utils/paraviewTreatments.py | 28 +- 6 files changed, 169 insertions(+), 160 deletions(-) diff --git a/geos-geomechanics/src/geos/geomechanics/model/MohrCircle.py b/geos-geomechanics/src/geos/geomechanics/model/MohrCircle.py index 67299c31..b0916da1 100644 --- a/geos-geomechanics/src/geos/geomechanics/model/MohrCircle.py +++ b/geos-geomechanics/src/geos/geomechanics/model/MohrCircle.py @@ -123,7 +123,7 @@ def setPrincipalComponents( self: Self, p3: float, p2: float, p1: float ) -> Non Raises: ValueError: Expected p3 <= p2 <= p1. """ - if ( p3 <= p2 ) and ( p2 <= p1 ): + if not ( ( p3 <= p2 ) and ( p2 <= p1 ) ): raise ValueError( "Component order is wrong. Expected p3 <= p2 <= p1." ) self.p3 = p3 self.p2 = p2 diff --git a/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py b/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py index 7db0adfa..d6efdfd5 100644 --- a/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py +++ b/geos-pv/src/geos/pv/plugins/PVMohrCirclePlot.py @@ -1,6 +1,6 @@ # 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 sys import logging @@ -22,17 +22,15 @@ from vtkmodules.vtkCommonCore import vtkDataArraySelection as vtkDAS from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector from vtkmodules.vtkCommonDataModel import ( - vtkUnstructuredGrid, -) + vtkUnstructuredGrid, ) -# update sys.path to load all GEOS Python Package dependencies +# Update sys.path to load all GEOS Python Package dependencies geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent sys.path.insert( 0, str( geos_pv_path / "src" ) ) from geos.pv.utils.config import update_paths update_paths() - from geos.geomechanics.model.MohrCircle import MohrCircle from geos.utils.enumUnits import Pressure, enumerationDomainUnit from geos.utils.GeosOutputsConstants import ( @@ -46,8 +44,6 @@ DEFAULT_ROCK_COHESION, ) from geos.mesh.utils.arrayHelpers import getArrayInObject -# from geos.mesh.utils.multiblockModifiers import mergeBlocks - import geos.pv.utils.mohrCircles.functionsMohrCircle as mcf import geos.pv.utils.paraviewTreatments as pvt @@ -67,7 +63,7 @@ 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. @@ -79,17 +75,18 @@ This plugin requires the presence of a `stressEffective` attribute in the mesh. Moreover, several timesteps should also be detected. -Please be aware that the number of cells and timesteps should be limited, or Paraview may crash due to overload. +.. 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 +* 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. +- 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. +* 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 @@ -97,8 +94,11 @@ * Apply. -.. WARNING:: - After first application of the Mohr circle filter, display properties, such as line colors, title, ticks, ... However the time steps and cell Ids cannot be calculated a second time. + +.. 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). """ @@ -115,40 +115,44 @@ 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 + # Create a new PythonView self.pythonView: Any = buildNewLayoutWithPythonView() - #: list of all cell ids + # List of all cell ids in the mesh self.cellIds: list[ str ] = [] - #: cell selection object + # List of all time steps + self.timeSteps: npt.NDArray[ np.float64 ] = np.array( [] ) + + # Cell selection object self.cellIdsDAS: vtkDAS = vtkDAS() self.cellIdsDAS.AddObserver( 0, createModifiedCallback( self ) ) - #: list of all time steps - self.timeSteps: npt.NDArray[ np.float64 ] = np.array( [] ) - - #: time steps selection object + # Time steps selection object self.timeStepsDAS: vtkDAS = vtkDAS() self.timeStepsDAS.AddObserver( 0, createModifiedCallback( self ) ) - #: list of all mohr circles - self.mohrCircles: list[ MohrCircle ] = [] + # Requested cell ids and time steps + self.requestedCellIds: list[ str ] = [] + self.requestedTimeStepsIndexes: list[ int ] = [] + + # List of mohr circles + self.mohrCircles: set[ MohrCircle ] = set() - #: failure envelop parameters + # Failure envelop parameters self.rockCohesion: float = DEFAULT_ROCK_COHESION self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD - #: stress convention (Geos: negative compression, Usual: positive) + # Stress convention (Geos: negative compression, Usual: positive) self.useGeosStressConvention: bool = True - #: curve aspect options - the same variables are set for each selected curve + # 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 @@ -156,7 +160,7 @@ def __init__( self: Self ) -> None: self.markerStyle: str = MarkerStyleEnum.NONE.optionValue self.markerSize: float = 1.0 - #: figure user choices + # Figure user choices self.userChoices: dict[ str, Any ] = { "xAxis": "Normal stress", "yAxis": "Shear stress", @@ -179,15 +183,11 @@ def __init__( self: Self ) -> None: "limMaxY": None, } - self.requestedCellIds: list[ str ] = [] - self.requestedTimeStepsIndexes: list[ int ] = [] - - #: request data processing step - incremented each time RequestUpdateExtent is called + # Request data processing step - incremented each time RequestUpdateExtent is called self.requestDataStep: int = -1 - #: logger - - self.logger = logging.getLogger( "MohrCircle" ) + # Logger + self.logger: logging.Logger = logging.getLogger( "MohrCircle" ) self.logger.setLevel( logging.INFO ) if not self.logger.hasHandlers(): handler = VTKHandler() @@ -195,47 +195,18 @@ def __init__( self: Self ) -> None: self.logger.addHandler( handler ) - def _getUserChoices( self: Self ) -> dict[ str, Any ]: - """Access the m_userChoices attribute. - - Returns: - dict[str] : 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, - } - @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.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" ) @@ -243,7 +214,7 @@ def a01GetCellIdsDAS( self: Self ) -> vtkDAS: """Get selected cell ids to plot. Returns: - vtkDataArraySelection: selected cell ids. + vtkDataArraySelection: Selected cell ids. """ return self.cellIdsDAS @@ -252,7 +223,7 @@ def a02GetTimestepsToPlot( self: Self ) -> vtkDAS: """Get selected time steps to plot. Returns: - vtkDataArraySelection: selected time steps. + vtkDataArraySelection: Selected time steps. """ return self.timeStepsDAS @@ -274,7 +245,7 @@ def b01SetCohesion( self: Self, value: float ) -> None: """Set rock cohesion. Args: - value (float): rock cohesion (Pa) + value (float): Rock cohesion (Pa). """ self.rockCohesion = value self.Modified() @@ -288,7 +259,7 @@ def b02SetFrictionAngle( self: Self, value: float ) -> None: """Set friction angle. Args: - value (float): friction angle (°). + value (float): Friction angle (°). """ self.frictionAngle = value * np.pi / 180.0 self.Modified() @@ -308,7 +279,7 @@ def b04SetStressUnit( self: Self, choice: int ) -> None: """Set stress unit. Args: - choice (int): stress unit index in Pressure enum. + choice (int): Stress unit index in Pressure enum. """ self.userChoices[ "stressUnit" ] = choice self.Modified() @@ -323,8 +294,7 @@ def b05SetStressCompressionConvention( self: Self, useGeosConvention: bool ) -> """Set stress compression convention in plots. Args: - useGeosConvention (bool): True is Geos convention, False is usual - geomechanical convention. + useGeosConvention (bool): True is Geos convention, False is usual geomechanical convention. """ # Specify if data is from GEOS self.useGeosStressConvention = useGeosConvention @@ -336,7 +306,8 @@ 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.userChoices[ "annotateCircles" ] = boolean self.Modified() @@ -347,7 +318,8 @@ 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.userChoices[ "minorticks" ] = boolean self.Modified() @@ -369,7 +341,8 @@ def c00SetModifyTitleAndLegend( self: Self, boolean: bool ) -> 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.userChoices[ "displayTitle" ] = boolean self.modifyTitleAndLegend = boolean @@ -379,7 +352,7 @@ def c01SetTitlePlot( self: Self, title: str ) -> None: """Set title. Args: - title (str): title. + title (str): Requested title. Defaults is "Mohr's circle". """ self.userChoices[ "title" ] = title self.Modified() @@ -390,7 +363,7 @@ 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.userChoices[ "titleStyle" ] = choice.optionValue @@ -402,7 +375,7 @@ 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.userChoices[ "titleWeight" ] = choice.optionValue @@ -414,7 +387,7 @@ 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.userChoices[ "titleSize" ] = size self.Modified() @@ -438,7 +411,7 @@ 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.userChoices[ "legendPosition" ] = choice.optionValue @@ -450,7 +423,7 @@ 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.userChoices[ "legendSize" ] = size self.Modified() @@ -472,7 +445,8 @@ 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.userChoices[ "customAxisLim" ] = boolean self.Modified() @@ -548,7 +522,8 @@ def f01SetModifyCurvesAspect( self: Self, boolean: bool ) -> 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.modifyCurvesAspect = boolean @@ -557,7 +532,7 @@ 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() return [ FAILURE_ENVELOPE ] + circleIds @@ -572,7 +547,7 @@ def f03SetCellID( self: Self, value: str ) -> None: """Set circle ids to use. Args: - value (str): circle ids. + value (str): Circle ids. """ self.circleIdUsed = value self.Modified() @@ -583,7 +558,7 @@ 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.lineStyle = choice.optionValue @@ -595,7 +570,7 @@ 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.lineWidth = value self.Modified() @@ -606,7 +581,7 @@ 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.markerStyle = choice.optionValue @@ -618,7 +593,7 @@ 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.markerSize = value self.Modified() @@ -671,9 +646,9 @@ def RequestInformation( """Inherited from VTKPythonAlgorithmBase::RequestInformation. Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects Returns: int: 1 if calculation successfully ended, 0 otherwise. @@ -685,7 +660,7 @@ def RequestInformation( if self.requestDataStep < 0: # Get cell ids inData = self.GetInputData( inInfoVec, 0, 0 ) - self.cellIds = pvt.getVtkOriginalCellIds( inData ) + self.cellIds = pvt.getVtkOriginalCellIds( inData, self.logger) # Update vtkDAS for circleId in self.cellIds: @@ -709,9 +684,9 @@ def RequestUpdateExtent( """Inherited from VTKPythonAlgorithmBase::RequestUpdateExtent. Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects Returns: int: 1 if calculation successfully ended, 0 otherwise. @@ -719,14 +694,11 @@ def RequestUpdateExtent( executive = self.GetExecutive() inInfo = inInfoVec[ 0 ] - if self.requestDataStep < 0: - self.mohrCircles.clear() - - # update requestDataStep + # Update requestDataStep self.requestDataStep += 1 - # update time according to requestDataStep iterator + # Update time according to requestDataStep iterator if self.requestDataStep == 0: - self._defineRequestedTimeSteps() + self._updateRequestedTimeSteps() if self.requestDataStep < len( self.timeSteps ): inInfo.GetInformationObject( 0 ).Set( @@ -742,7 +714,6 @@ def RequestUpdateExtent( self.Modified() return 1 - def RequestDataObject( self: Self, request: vtkInformation, @@ -752,9 +723,9 @@ def RequestDataObject( """Inherited from VTKPythonAlgorithmBase::RequestDataObject. Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects + request (vtkInformation): Request. + inInfoVec (list[vtkInformationVector]): Input objects. + outInfoVec (vtkInformationVector): Output objects. Returns: int: 1 if calculation successfully ended, 0 otherwise. @@ -768,7 +739,6 @@ def RequestDataObject( outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] - def RequestData( self: Self, request: vtkInformation, # noqa: F841 @@ -778,20 +748,20 @@ def RequestData( """Inherited from VTKPythonAlgorithmBase::RequestData. Args: - request (vtkInformation): Request - inInfoVec (list[vtkInformationVector]): Input objects - outInfoVec (vtkInformationVector): Output objects + request (vtkInformation): Request. + inInfoVec (list[vtkInformationVector]): Input objects. + outInfoVec (vtkInformationVector): Output objects. Returns: int: 1 if calculation successfully ended, 0 otherwise. """ try: - # input: Union[ vtkUnstructuredGrid, vtkMultiBlockDataSet ] = self.GetInputData( inInfoVec, 0, 0 ) - input: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) - assert input is not None, "Input data is undefined" - + inputMesh: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) executive = self.GetExecutive() + if self.requestDataStep == 1: + self.logger.info( "Computing Mohr circles for requested time steps and cell Ids." ) + if self.requestDataStep < len( self.timeSteps ): request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore[no-any-return] currentTimeStep: float = ( @@ -800,31 +770,27 @@ def RequestData( ) if self.requestDataStep in self.requestedTimeStepsIndexes: - self.mohrCircles.extend( self._createMohrCirclesAtTimeStep( input, currentTimeStep ) ) + self.mohrCircles.update( self._createMohrCirclesAtTimeStep( inputMesh, currentTimeStep ) ) # Plot mohr circles else: + self.logger.info( "Displaying Mohr's Circles" ) # Displayed time step, no need to go further request.Remove( executive.CONTINUE_EXECUTING() ) # type: ignore[no-any-return] assert self.pythonView is not None, "No Python View was found." self._defineCurvesAspect() - # mohrCircles: list[ MohrCircle ] = self._filterMohrCircles() + mohrCircles: list[ MohrCircle ] = self._filterMohrCircles() self.pythonView.Script = mcf.buildPythonViewScript( geos_pv_path, - self.mohrCircles, + mohrCircles, self.rockCohesion, self.frictionAngle, self._getUserChoices(), ) Render() - except ( ValueError, TypeError ) as e: - self.logger.error( f"MergeBlock failed due to {e}", - exc_info=True ) #no critical as there is no reason to crash here - return 0 - except Exception as e: self.logger.error( "Mohr circles cannot be plotted due to:" ) self.logger.error( e ) @@ -833,10 +799,9 @@ def RequestData( def _createMohrCirclesAtTimeStep( self: Self, - # mesh: Union[ vtkUnstructuredGrid, vtkMultiBlockDataSet ], mesh: vtkUnstructuredGrid, currentTimeStep: float, - ) -> list[ MohrCircle ]: + ) -> set[ MohrCircle ]: """Create mohr circles of all cells at the current time step. Args: @@ -854,12 +819,11 @@ def _createMohrCirclesAtTimeStep( stressConvention = StressConventionEnum.GEOS_STRESS_CONVENTION if self.useGeosStressConvention else StressConventionEnum.COMMON_STRESS_CONVENTION # Get the cell IDs requested by the user - self._defineRequestedCellIds() + self._updateRequestedCellIds() - return mcf.createMohrCircleAtTimeStep( stressArray, self.requestedCellIds , str( currentTimeStep ), + 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. @@ -870,15 +834,43 @@ def _filterMohrCircles( self: Self ) -> list[ MohrCircle ]: circleIds: list[ str ] = self._getCircleIds() return [ mohrCircle for mohrCircle in self.mohrCircles if mohrCircle.getCircleId() in circleIds ] - - def _defineRequestedTimeSteps( self: Self ): + def _updateRequestedTimeSteps( self: Self ) -> None: + """Update the requestedTimeStepsIndexes attribute from user choice.""" requestedTimeSteps: list[ str ] = pvt.getArrayChoices( self.a02GetTimestepsToPlot() ) - requestedTimeStepsIndexes = [ pvt.getTimeStepIndex( float( ts ), self.timeSteps ) for ts in requestedTimeSteps ] + 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 - self.requestedTimeStepsIndexes: list[ int ] = requestedTimeStepsIndexes + 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() ) - def _defineRequestedCellIds( self: Self ): - self.requestedCellIds: list[ str ] = pvt.getArrayChoices( self.a01GetCellIdsDAS() ) + 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 index 761b9894..2627d2a6 100644 --- a/geos-pv/src/geos/pv/utils/mohrCircles/__init__.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/__init__.py @@ -1,2 +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-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py b/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py index 1e4578b6..fafe7228 100644 --- a/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/functionsMohrCircle.py @@ -1,6 +1,6 @@ # 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 @@ -15,7 +15,7 @@ ) __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. """ @@ -31,7 +31,7 @@ class StressConventionEnum( Enum ): def buildPythonViewScript( - dir_path: str, + dirpath: str, mohrCircles: list[ MohrCircle ], rockCohesion: float, frictionAngle: float, @@ -39,20 +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 - 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 + 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. 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() ) ) @@ -74,11 +73,11 @@ def findAnnotateTuples( mohrCircle: MohrCircle, ) -> tuple[ str, str, tuple[ flo Args: mohrCircle (MohrCircle): Mohr's circle to consider. - maxTau (float): Max shear stress + maxTau (float): Max shear stress. Returns: tuple[str, str, tuple[float, float], tuple[float, float]]: Labels and - location of labels. + location of labels. """ p3, _, p1 = mohrCircle.getPrincipalComponents() xMaxDisplay: str = f"{p1:.2E}" @@ -90,14 +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 + cellId (str): Cell ID. timeStep (str): Time step. Returns: - str: Mohr's circle ID + str: Mohr's circle ID. """ return f"Cell_{cellId}@{timeStep}" @@ -122,7 +121,7 @@ def createMohrCircleAtTimeStep( Returns: set[MohrCircle]: Set of MohrCircle objects. """ - if stressArray.shape[ 1 ] == 6: + 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." ) diff --git a/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py b/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py index 9b21b5a7..28cc9e49 100644 --- a/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py +++ b/geos-pv/src/geos/pv/utils/mohrCircles/plotMohrCircles.py @@ -172,7 +172,7 @@ def _updateAxis( ax: Axes, userChoices: dict[ str, Any ] ) -> None: """ # 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 unitChoice: int = userChoices.get( "stressUnit", 0 ) diff --git a/geos-pv/src/geos/pv/utils/paraviewTreatments.py b/geos-pv/src/geos/pv/utils/paraviewTreatments.py index 996cf02f..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,11 +10,11 @@ import numpy.typing as npt import pandas as pd # type: ignore[import-untyped] -from packaging.version import Version - 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, @@ -36,6 +37,7 @@ ComponentNameEnum, GeosMeshOutputsEnum, ) +from geos.utils.Logger import ( CustomLoggerFormatter ) from geos.mesh.utils.multiblockModifiers import mergeBlocks # valid sources for Python view configurator @@ -455,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, @@ -469,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 = mergeBlocks( 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"