Source code for geos.pv.utils.mohrCircles.plotMohrCircles

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# 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.typing as npt

from matplotlib import ticker
from matplotlib.axes import Axes  # type: ignore[import-untyped]
from matplotlib.figure import Figure  # type: ignore[import-untyped]
from matplotlib.lines import Line2D  # type: ignore[import-untyped]

import geos.pv.utils.mohrCircles.functionsMohrCircle as mcf
from geos.pv.pyplotUtils.matplotlibOptions import (
    FontStyleEnum,
    FontWeightEnum,
    LegendLocationEnum,
    LineStyleEnum,
    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
objects respectively.
"""


[docs] def createMohrCirclesFigure( mohrCircles: list[ MohrCircle ], mohrCoulomb: MohrCoulomb, userChoices: dict[ str, Any ] ) -> Figure: """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. Returns: Figure: Figure object """ plt.close() # Create figure fig, ax = plt.subplots( constrained_layout=True ) # 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 failureEnvelopeAspect: dict[ str, Any ] = curvesAspect.get( FAILURE_ENVELOPE, {} ) _plotMohrCoulomb( ax, mohrCoulomb, failureEnvelopeAspect ) # Set user preferences _setUserChoices( ax, userChoices ) return fig
def _plotMohrCircles( ax: Axes, mohrCircles: list[ MohrCircle ], circlesAspect: dict[ str, dict[ str, Any ] ], annotate: bool, ) -> None: """Plot multiple Mohr's circles on input Axes. 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. """ 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: 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 if label in circlesAspect: circleAspect: dict[ str, Any ] = circlesAspect[ label ] color: tuple[ float, float, float ] = circleAspect.get( "color", "k" ) linestyle: str = circleAspect.get( "linestyle", LineStyleEnum.SOLID.optionValue ) linewidth: float = circleAspect.get( "linewidth", 1 ) marker: str = circleAspect.get( "marker", MarkerStyleEnum.NONE.optionValue ) markersize: float = circleAspect.get( "markersize", 1 ) p = ax.plot( xCoords, yCoords, label=label, color=color, linestyle=linestyle, linewidth=linewidth, marker=marker, markersize=markersize, ) else: p = ax.plot( xCoords, yCoords, label=label, linestyle="-", marker="" ) if annotate: fontColor = p[ 0 ].get_color() annot: tuple[ str, str, tuple[ float, float ], tuple[ float, float ] ] = ( mcf.findAnnotateTuples( mohrCircle ) ) ax.annotate( annot[ 0 ], xy=annot[ 2 ], ha="left", rotation=30, color=fontColor ) ax.annotate( annot[ 1 ], xy=annot[ 3 ], ha="right", rotation=30, color=fontColor ) def _plotMohrCoulomb( ax: Axes, mohrCoulomb: MohrCoulomb, curvesAspect: dict[ str, Any ] ) -> None: """Plot Mohr-Coulomb failure envelope in input Axes object. Args: ax (Axes): Axes where to plot 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 ) color: tuple[ float, float, float ] = curvesAspect.get( "color", "k" ) linestyle: str = curvesAspect.get( "linestyle", LineStyleEnum.SOLID.optionValue ) linewidth: float = curvesAspect.get( "linewidth", 1 ) marker: str = curvesAspect.get( "marker", MarkerStyleEnum.NONE.optionValue ) markersize: float = curvesAspect.get( "markersize", 1 ) ax.plot( principalStresses, shearStress, label="Failure Envelope", color=color, linestyle=linestyle, linewidth=linewidth, marker=marker, markersize=markersize, ) def _setUserChoices( ax: Axes, userChoices: dict[ str, Any ] ) -> None: """Set user preferences on input Axes. Args: ax (Axes): Axes object to modify. userChoices (dict[str, Any]): User-defined properties. """ _updateAxis( ax, userChoices ) # Set title properties if userChoices.get( "displayTitle", False ): updateTitle( ax, userChoices ) # Set legend if userChoices.get( "displayLegend", False ): _updateLegend( ax, userChoices ) if userChoices.get( "customAxisLim", False ): _updateAxisLimits( ax, userChoices ) def _updateAxis( ax: Axes, userChoices: dict[ str, Any ] ) -> None: """Update axis ticks and labels. Args: ax (Axes): Axes object. userChoices (dict[str, Any]): User-defined properties. """ # Update axis labels xlabel: str = userChoices.get( "xAxis", "Normal stress" ) ylabel: str = userChoices.get( "yAxis", "Shear stress" ) # Get unit unitChoice: int = userChoices.get( "stressUnit", 0 ) unitObj: Unit = list( Pressure )[ unitChoice ].value unitLabel: str = unitObj.unitLabel # Change displayed units xlabel += f" ({unitLabel})" ylabel += f" ({unitLabel})" ax.set_xlabel( xlabel ) ax.set_ylabel( ylabel ) # 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 ax.xaxis.set_major_formatter( ticker.FuncFormatter( _tickFormatterFunc ) ) ax.yaxis.set_major_formatter( ticker.FuncFormatter( _tickFormatterFunc ) ) # Set axis properties ax.set_aspect( "equal", anchor="C" ) xmin, xmax = ax.get_xlim() ax.set_xlim( 0.0 ) ax.set_ylim( 0.0, xmax ) ax.grid() ax.minorticks_off() if "minorticks" in userChoices and userChoices[ "minorticks" ]: ax.minorticks_on()
[docs] def updateTitle( ax: Axes, userChoices: dict[ str, Any ] ) -> None: """Update title. Args: ax (Axes): Axes object. userChoices (dict[str, Any]): User-defined properties. """ 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 )
def _updateLegend( ax: Axes, userChoices: dict[ str, Any ] ) -> None: """Update legend. Args: ax (Axes): Axes object. userChoices (dict[str, Any]): User-defined properties. """ loc = userChoices.get( "legendPosition", LegendLocationEnum.BEST.optionValue ) size = userChoices.get( "legendSize", 10 ) ax.legend( loc=loc, fontsize=size ) def _updateAxisLimits( ax: Axes, userChoices: dict[ str, Any ] ) -> None: """Update axis limits. Args: ax (Axes): Axes object. userChoices (dict[str, Any]): User-defined properties. """ xmin, xmax = ax.get_xlim() if userChoices.get( "limMinX" ) is not None: xmin = userChoices[ "limMinX" ] if userChoices.get( "limMaxX" ) is not None: xmax = userChoices[ "limMaxX" ] ax.set_xlim( xmin, xmax ) ymin, ymax = ax.get_xlim() if userChoices.get( "limMinY" ) is not None: ymin = userChoices[ "limMinY" ] if userChoices.get( "limMaxY" ) is not None: ymax = userChoices[ "limMaxY" ] ax.set_ylim( ymin, ymax )