Introduction
In Part 7 , we saw that
for isotropic elastic materials and perfect associated plasticity, the trial stress
and the actual stress are at the shortest distance from each other in a transformed stress
space. We also saw that the transformed stress can be expressed as
$$
\boldsymbol{\sigma}^\star
= \frac{z}{\sqrt{3\kappa}}\,\mathbf{E}_z +
\frac{r}{\sqrt{2\mu}}\,\mathbf{E}_r
$$
where
$$
z = \tfrac{1}{\sqrt{3}}\,\text{tr}(\boldsymbol{\sigma}) ~,~~
r = \lVert \boldsymbol{s} \rVert
$$
and
$$
\mathbf{E}_z = \tfrac{1}{\sqrt{3}}\,\mathbf{I} ~,~~
\mathbf{E}_r = \frac{\boldsymbol{s}}{\lVert\boldsymbol{s}\rVert} \,.
$$
We can show that the transformed stress vector remains geometrically unchanged (in the sense that
angles are unchanged) if we express it as
$$
\boldsymbol{\sigma}^\star
= z\,\mathbf{E}_z + \sqrt{\frac{3\kappa}{2\mu}}\,r\,\mathbf{E}_r
=: z\,\mathbf{E}_z + r'\,\mathbf{E}_r
$$
This observation can be used to perform a purely geometric stress update that can
be quite efficient for nonlinear Drucker-Prager plasticity and many other phenomenological
plasticity models. Let us see how this concept has been applied to the Arena plasticity
model .
The Arena yield function
The Arena model is an extension to partially saturated soils of the Arenisca model
developed by R. M. Brannon and co-workers. The yield function used by this model is a
nonlinear Drucker-Prager model with a compression cap. Further details can be found in
the Arena theory manual .
If the volumetric and deviatoric components of the total stress are
$$
\bar{p} := -\tfrac{1}{3} \text{tr}(\boldsymbol{\sigma}) \quad \text{and} \quad \boldsymbol{s} := \boldsymbol{\sigma} + \bar{p} \mathbf{I}
$$
we can define
$$
\begin{align}
\bar{p}_\text{eff} & := -\tfrac{1}{3} \text{tr}(\boldsymbol{\sigma}_\text{eff}) = \bar{p} - B \bar{p}^w \\
\boldsymbol{s}_\text{eff} & := \boldsymbol{\sigma}_\text{eff} + \bar{p}_\text{eff} \mathbf{I} = \boldsymbol{\sigma} + \bar{p} \mathbf{I} = \boldsymbol{s} \\
J_2^\text{eff} & := \tfrac{1}{2} \boldsymbol{s}_\text{eff}:\boldsymbol{s}_\text{eff} \,.
\end{align}
$$
Then the Arena yield function is
$$
f(\boldsymbol{\sigma}, B, \bar{p}^w, \bar{X}) =
\sqrt{J_2^\text{eff}} - F_f(\bar{p}_\text{eff}) \, F_c(\bar{p}_\text{eff}, \bar{X})
$$
where
$$
F_f(\bar{p}_\text{eff}) = a_1 - a_3 \exp[- 3 a_2 \bar{p}_\text{eff}] + 3 a_4 \bar{p}_\text{eff}
$$
and
$$
F_c(\bar{p}_\text{eff}, \bar{X}) =
\begin{cases}
1 & \quad \text{for}\quad 3\bar{p}_\text{eff} \le \bar{\kappa} \\
\sqrt{1 - \left(\cfrac{3\bar{p}_\text{eff} - \bar{\kappa}}{\bar{X}_\text{eff} - \bar{\kappa}}\right)^2} &
\quad \text{for}\quad 3\bar{p}_\text{eff} > \bar{\kappa} \,.
\end{cases}
$$
Here \(a_i\) are material parameters, \(\bar{X}_\text{eff}(\boldsymbol{\varepsilon}^p, B, \bar{p}^w) = \bar{X} - 3B\bar{p}^w\) is the
shifted form of the apparent hydrostatic compressive strength (\(\bar{X}/3\)) of the partially saturated
material, and \(\bar{\kappa}\) is the branch point at which the cap function \(F_c\) starts decreasing until it
reaches the hydrostatic strength point (\(\bar{X}\)):
$$
\bar{\kappa} = 3\bar{p}_\text{eff}^\text{peak} - (3\bar{p}_\text{eff}^\text{peak} - \bar{X}_\text{eff}) R_c
$$
where \(\bar{p}_\text{eff}^\text{peak}\) is the maximum hydrostatic tensile stress that the material can
support and \(R_c\) is a cap ratio.
The non-hardening return algorithm
We use the closest-point return approach discussed in the previous articles in this series to
compute a return to the yield surface while keeping all internal variables fixed.
The pseudocsode of the algorithm is listed below:
$$
\begin{align}
&\text{Require:} && \boldsymbol{\sigma}^k, \delta\boldsymbol{\varepsilon}, X^k, K^k, G^k,
(\bar{p}^w)^k, \boldsymbol{s}^\text{trial}, \sqrt{J_2^\text{trial}},
r^\text{trial}, z_\text{eff}^\text{trial}, \\
& && a_1, a_2, a_3, a_4, I_1^\text{peak}, R_c \\
&\text{Procedure:} && \text{nonHardeningReturn} \\
& 1.\quad && r'_\text{trial} \leftarrow r^\text{trial} \sqrt{\cfrac{3K^k}{2G^k}}
\qquad \mbox{Transform the trial $r$ coordinate} \\
& 2.\quad && X_\text{eff}^k \leftarrow X^k + 3 (\bar{p}^w)^k \\
& 3.\quad && z_\text{eff}^\text{closest}, r'_\text{closest} \leftarrow
\text{getClosestPoint}(K^k, G^k, X_\text{eff}^k, a_1, a_2, a_3, a_4,\\
& && \qquad \qquad\qquad I_1^\text{peak}, R_c, z_\text{eff}^\text{trial}, r'_\text{trial}) \\
& 4.\quad && I_1^{\text{closest}} \leftarrow \sqrt{3} z_\text{eff}^\text{closest} - 3 (\bar{p}^w)^k,
\sqrt{J_2^\text{closest}} \leftarrow \sqrt{\frac{G^k}{3K^k}}\,r'_\text{closest} \\
& 5.\quad && \text{If} {\sqrt{J_2^\text{trial}} > 0} \\
& 6. && \qquad \boldsymbol{\sigma}^\text{fixed} = \tfrac{1}{3} I_1^{\text{closest}} \mathbf{I} +
\frac{\sqrt{J_2^\text{closest}}}{\sqrt{J_2^\text{trial}}}\,\boldsymbol{s}^\text{trial} \\
& && \qquad \mbox{Compute updated total stress} \\
& 7.\quad && \text{Else} \\
& 8. && \qquad \boldsymbol{\sigma}^\text{fixed} = \tfrac{1}{3} I_1^{\text{closest}} \mathbf{I} +
\boldsymbol{s}^\text{trial}\\
& && \qquad
\mbox{Compute updated total stress from hydrostatic trial stress} \\
& 9.\quad && \text{EndIf} \\
& 10.\quad && \delta\boldsymbol{\sigma}_\text{fixed} \leftarrow \boldsymbol{\sigma}^\text{fixed} -
\boldsymbol{\sigma}^k \qquad \mbox{Compute stress increment} \\
& 11.\quad && \delta\boldsymbol{\sigma}_\text{fixed}^\text{iso} \leftarrow \tfrac{1}{3}
\text{tr}(\delta\boldsymbol{\sigma}_\text{fixed}) \mathbf{I},
\quad \delta\boldsymbol{\sigma}_\text{fixed}^\text{dev} \leftarrow
\delta\boldsymbol{\sigma}_\text{fixed} - \delta\boldsymbol{\sigma}_\text{fixed}^\text{iso} \\
& 12.\quad && \delta\boldsymbol{\varepsilon}^{p,\text{fixed}} = \delta\boldsymbol{\varepsilon} -
\frac{1}{3K^k}\,\delta\boldsymbol{\sigma}_\text{fixed}^\text{iso} -
\frac{1}{2G^k}\,\delta\boldsymbol{\sigma}_\text{fixed}^\text{dev} \\
& && \quad\mbox{Compute plastic strain increment} \\
& 13.\quad && \text{Return} \qquad \boldsymbol{\sigma}^\text{fixed}, \delta\boldsymbol{\varepsilon}^{p,\text{fixed}} \\
\end{align}
$$
The closest point algorithm
Let us look at the actual implementation to get a feel for how the closest point can be found
geometrically.
bool
YieldCond_Arena :: getClosestPoint ( const ModelState_Arena * state , // The plasticity state
const double & px , // The trial stress z, r'
const double & py ,
double & cpx , // The closest point z, r'
double & cpy )
{
Point pt ( px , py , 0.0 );
Point closest ( 0.0 , 0.0 , 0.0 );
getClosestPointGeometricBisect ( state , pt , closest );
cpx = closest . x ();
cpy = closest . y ();
return true ;
}
The bisection algorithm for the closest point can be implemented as shown below.
void
YieldCond_MasonSand :: getClosestPointGeometricBisect ( const ModelState_Arena * state ,
const Point & z_r_pt ,
Point & z_r_closest )
{
// Get the particle specific internal variables from the model state
// Store in a local struct
d_local . PEAKI1 = state -> yieldParams . at ( "PEAKI1" );
................
std :: vector < double > limitParameters =
computeModelParameters ( d_local . PEAKI1 , d_local . FSLOPE , d_local . STREN , d_local . YSLOPE );
d_local . a1 = limitParameters [ 0 ];
d_local . a2 = limitParameters [ 1 ];
................
// Get the plastic internal variables from the model state
double pbar_w = state -> pbar_w ;
double X_eff = state -> capX + 3.0 * pbar_w ;
// Compute kappa
double I1_diff = d_local . PEAKI1 - X_eff ;
double kappa = d_local . PEAKI1 - d_local . CR * I1_diff ;
// Get the bulk and shear moduli and compute sqrt(3/2 K/G)
double sqrtKG = std :: sqrt ( 1.5 * state -> bulkModulus / state -> shearModulus );
// Compute diameter of yield surface in z-r space
double sqrtJ2_diff = 2.0 * evalYieldConditionMax ( state );
double yield_surf_dia_zrprime = std :: max ( I1_diff * one_sqrt_three , sqrtJ2_diff * sqrt_two * sqrtKG );
double dist_to_trial_zr = std :: sqrt ( z_r_pt . x () * z_r_pt . x () + z_r_pt . y () * z_r_pt . y ());
double dist_dia_ratio = dist_to_trial_zr / yield_surf_dia_zrprime ;
// Set the number of points used to discretize the yield surface
int num_points = std :: max ( 5 , ( int ) std :: ceil ( std :: log ( dist_dia_ratio )));
// Set up I1 limits
double I1eff_min = X_eff ;
double I1eff_max = d_local . PEAKI1 ;
// Set up bisection
double eta_lo = 0.0 , eta_hi = 1.0 ;
// Set up mid point
double I1eff_mid = 0.5 * ( I1eff_min + I1eff_max );
double eta_mid = 0.5 * ( eta_lo + eta_hi );
// Do bisection
int iters = 1 ;
double TOLERANCE = 1.0e-10 ;
std :: vector < Point > z_r_points ;
std :: vector < Point > z_r_segments ;
std :: vector < Point > z_r_segment_points ;
Point z_r_closest_old ;
z_r_closest_old . x ( std :: numeric_limits < double >:: max ());
z_r_closest_old . y ( std :: numeric_limits < double >:: max ());
z_r_closest_old . z ( 0.0 );
while ( std :: abs ( eta_hi - eta_lo ) > TOLERANCE ) {
// Get the yield surface points
z_r_points . clear ();
getYieldSurfacePointsAll_RprimeZ ( X_eff , kappa , sqrtKG , I1eff_min , I1eff_max ,
num_points , z_r_points );
// Find the closest point
findClosestPoint ( z_r_pt , z_r_points , z_r_closest );
// Compute I1 for the closest point
double I1eff_closest = sqrt_three * z_r_closest . x ();
// If (I1_closest < I1_mid)
if ( I1eff_closest < I1eff_mid ) {
I1eff_max = I1eff_mid ;
eta_hi = eta_mid ;
} else {
I1eff_min = I1eff_mid ;
eta_lo = eta_mid ;
}
I1eff_mid = 0.5 * ( I1eff_min + I1eff_max );
eta_mid = 0.5 * ( eta_lo + eta_hi );
// Distance to old closest point
if ( iters > 10 && ( z_r_closest - z_r_closest_old ). length2 () < 1.0e-16 ) {
break ;
}
z_r_closest_old = z_r_closest ;
++ iters ;
}
return ;
}
The yield surface points are computed using
void
YieldCond_MasonSand :: getYieldSurfacePointsAll_RprimeZ ( const double & X_eff ,
const double & kappa ,
const double & sqrtKG ,
const double & I1eff_min ,
const double & I1eff_max ,
const int & num_points ,
std :: vector < Point >& z_r_vec )
{
// Compute z_eff and r'
computeZeff_and_RPrime ( X_eff , kappa , sqrtKG , I1eff_min , I1eff_max , num_points , z_r_vec );
return ;
}
void
YieldCond_MasonSand :: computeZeff_and_RPrime ( const double & X_eff ,
const double & kappa ,
const double & sqrtKG ,
const double & I1eff_min ,
const double & I1eff_max ,
const int & num_points ,
std :: vector < Point >& z_r_vec )
{
// Set up points
double rad = 0.5 * ( d_local . PEAKI1 - X_eff );
double cen = 0.5 * ( d_local . PEAKI1 + X_eff );
double theta_max = std :: acos ( std :: max (( I1eff_min - cen ) / rad , - 1.0 ));
double theta_min = std :: acos ( std :: min (( I1eff_max - cen ) / rad , 1.0 ));
std :: vector < double > theta_vec ;
linspace ( theta_min , theta_max , num_points , theta_vec );
for ( auto theta : theta_vec ) {
double I1_eff = std :: max ( cen + rad * std :: cos ( theta ), X_eff );
// Compute F_f
double Ff = d_local . a1 - d_local . a3 * std :: exp ( d_local . a2 * I1_eff ) - d_local . a4 * ( I1_eff );
double Ff_sq = Ff * Ff ;
// Compute Fc
double Fc_sq = 1.0 ;
if ( I1_eff < kappa ) {
double ratio = ( kappa - I1_eff ) / ( kappa - X_eff );
Fc_sq = std :: max ( 1.0 - ratio * ratio , 0.0 );
}
// Compute J2
double J2 = Ff_sq * Fc_sq ;
z_r_vec . push_back ( Point ( I1_eff / sqrt_three ,
std :: sqrt ( 2.0 * J2 ) * sqrtKG , 0.0 ));
}
return ;
}
And, finally, the actual geometric closest point algorithm is
void
YieldCond_MasonSand :: findClosestPoint ( const Point & p ,
const std :: vector < Point >& poly ,
Point & min_p )
{
double TOLERANCE_MIN = 1.0e-12 ;
std :: vector < Point > XP ;
// Loop through the segments of the polyline
auto iterStart = poly . begin ();
auto iterEnd = poly . end ();
auto iterNext = iterStart ;
++ iterNext ;
for ( ; iterNext != iterEnd ; ++ iterStart , ++ iterNext ) {
Point start = * iterStart ;
Point next = * iterNext ;
// Find shortest distance from point to the polyline line
Vector m = next - start ;
Vector n = p - start ;
if ( m . length2 () < TOLERANCE_MIN ) {
XP . push_back ( start );
} else {
const double t0 = Dot ( m , n ) / Dot ( m , m );
if ( t0 <= 0.0 ) {
XP . push_back ( start );
} else if ( t0 >= 1.0 ) {
XP . push_back ( next );
} else {
// Shortest distance is inside segment; this is the closest point
min_p = m * t0 + start ;
XP . push_back ( min_p );
}
}
}
double min_d = std :: numeric_limits < double >:: max ();
for ( const auto & xp : XP ) {
// Compute distance sq
double dSq = ( p - xp ). length2 ();
if ( dSq < min_d ) {
min_d = dSq ;
min_p = xp ;
}
}
return ;
}
An animation of the closest point algorithm
This geometric algorithm is remarkably accurate and avoids complications associated with
computing gradients in the transformed space. In the next article we will discuss
how the animation in this was created.