bool
TensorMechanicsPlasticTensileMulti::doReturnMap(const RankTwoTensor & trial_stress, const Real & intnl_old, const RankFourTensor & E_ijkl,
                                                Real /*ep_plastic_tolerance*/, RankTwoTensor & returned_stress, Real & returned_intnl,
                                                std::vector<Real> & dpm, RankTwoTensor & delta_dp, std::vector<Real> & yf,
                                                bool & trial_stress_inadmissible) const
{
  mooseAssert(dpm.size() == 3, "TensorMechanicsPlasticTensileMulti size of dpm should be 3 but it is " << dpm.size());

  std::vector<Real> eigvals;
  RankTwoTensor eigvecs;
  trial_stress.symmetricEigenvaluesEigenvectors(eigvals, eigvecs);
  eigvals[0] += _shift;
  eigvals[2] -= _shift;

  Real str = tensile_strength(intnl_old);

  yf.resize(3);
  yf[0] = eigvals[0] - str;
  yf[1] = eigvals[1] - str;
  yf[2] = eigvals[2] - str;

  if (yf[0] <= _f_tol && yf[1] <= _f_tol && yf[2] <= _f_tol)
  {
    // purely elastic (trial_stress, intnl_old)
    trial_stress_inadmissible = false;
    return true;
  }

  trial_stress_inadmissible = true;
  delta_dp.zero();
  returned_stress.zero();

  // In the following i often assume that E_ijkl is
  // for an isotropic situation.  This reduces FLOPS
  // substantially which is important since the returnMap
  // is potentially the most compute-intensive function
  // of a simulation.
  // In many comments i write the general expression, and
  // i hope that might guide future coders if they are
  // generalising to a non-istropic E_ijkl

  // n[alpha] = E_ijkl*r[alpha]_kl expressed in principal stress space
  // (alpha = 0, 1, 2, corresponding to the three surfaces)
  // Note that in principal stress space, the flow
  // directions are, expressed in 'vector' form,
  // r[0] = (1,0,0), r[1] = (0,1,0), r[2] = (0,0,1).
  // Similar for _n:
  // so _n[0] = E_ij00*r[0], _n[1] = E_ij11*r[1], _n[2] = E_ij22*r[2]
  // In the following I assume that the E_ijkl is
  // for an isotropic situation.
  // In the anisotropic situation, we couldn't express
  // the flow directions as vectors in the same principal
  // stress space as the stress: they'd be full rank-2 tensors
  std::vector<std::vector<Real> > n(3);
  for (unsigned i = 0 ; i < 3 ; ++i)
    n[i].resize(3);
  n[0][0] = E_ijkl(0,0,0,0);
  n[0][1] = E_ijkl(1,1,0,0);
  n[0][2] = E_ijkl(2,2,0,0);
  n[1][0] = E_ijkl(0,0,1,1);
  n[1][1] = E_ijkl(1,1,1,1);
  n[1][2] = E_ijkl(2,2,1,1);
  n[2][0] = E_ijkl(0,0,2,2);
  n[2][1] = E_ijkl(1,1,2,2);
  n[2][2] = E_ijkl(2,2,2,2);


  // With non-zero Poisson's ratio and hardening
  // it is not computationally cheap to know whether
  // the trial stress will return to the tip, edge,
  // or plane.  The following is correct for zero
  // Poisson's ratio and no hardening, and at least
  // gives a not-completely-stupid guess in the
  // more general case.
  // trial_order[0] = type of return to try first
  // trial_order[1] = type of return to try second
  // trial_order[2] = type of return to try third
  std::vector<int> trial_order(3);
  if (yf[0] > 0) // all the yield functions are positive, since eigvals are ordered eigvals[0] <= eigvals[1] <= eigvals[2]
  {
    trial_order[0] = tip;
    trial_order[1] = edge;
    trial_order[2] = plane;
  }
  else if (yf[1] > 0)  // two yield functions are positive
  {
    trial_order[0] = edge;
    trial_order[1] = tip;
    trial_order[2] = plane;
  }
  else
  {
    trial_order[0] = plane;
    trial_order[1] = edge;
    trial_order[2] = tip;
  }

  unsigned trial;
  bool nr_converged;
  for (trial = 0 ; trial < 3 ; ++trial)
  {
    switch (trial_order[trial])
    {
      case tip:
        nr_converged = returnTip(eigvals, n, dpm, returned_stress, intnl_old, 0);
        break;
      case edge:
        nr_converged = returnEdge(eigvals, n, dpm, returned_stress, intnl_old, 0);
        break;
      case plane:
        nr_converged = returnPlane(eigvals, n, dpm, returned_stress, intnl_old, 0);
        break;
    }
    str = tensile_strength(intnl_old + dpm[0] + dpm[1] + dpm[2]);
    if (nr_converged && KuhnTuckerOK(returned_stress, dpm, str))
      break;
  }

  if (trial == 3)
  {
    Moose::err << "Trial stress = \n";
    trial_stress.print(Moose::err);
    Moose::err << "Internal parameter = " << intnl_old << "\n";
    mooseError("TensorMechanicsPlasticTensileMulti: FAILURE!  You probably need to implement a line search\n");
    // failure - must place yield function values at trial stress into yf
    str = tensile_strength(intnl_old);
    yf[0] = eigvals[0] - str;
    yf[1] = eigvals[1] - str;
    yf[2] = eigvals[2] - str;
    return false;
  }

  // success

  returned_intnl = intnl_old;
  for (unsigned i = 0 ; i < 3 ; ++i)
  {
    yf[i] = returned_stress(i, i) - str;
    delta_dp(i, i) = dpm[i];
    returned_intnl += dpm[i];
  }
  returned_stress = eigvecs*returned_stress*(eigvecs.transpose());
  delta_dp = eigvecs*delta_dp*(eigvecs.transpose());
  return true;
}
bool
TensorMechanicsPlasticMohrCoulombMulti::doReturnMap(const RankTwoTensor & trial_stress, Real intnl_old, const RankFourTensor & E_ijkl,
                                                    Real ep_plastic_tolerance, RankTwoTensor & returned_stress, Real & returned_intnl,
                                                    std::vector<Real> & dpm, RankTwoTensor & delta_dp, std::vector<Real> & yf,
                                                    bool & trial_stress_inadmissible) const
{
  mooseAssert(dpm.size() == 6, "TensorMechanicsPlasticMohrCoulombMulti size of dpm should be 6 but it is " << dpm.size());

  std::vector<Real> eigvals;
  RankTwoTensor eigvecs;
  trial_stress.symmetricEigenvaluesEigenvectors(eigvals, eigvecs);
  eigvals[0] += _shift;
  eigvals[2] -= _shift;

  Real sinphi = std::sin(phi(intnl_old));
  Real cosphi = std::cos(phi(intnl_old));
  Real coh = cohesion(intnl_old);
  Real cohcos = coh*cosphi;

  yieldFunctionEigvals(eigvals[0], eigvals[1], eigvals[2], sinphi, cohcos, yf);

  if (yf[0] <= _f_tol && yf[1] <= _f_tol && yf[2] <= _f_tol && yf[3] <= _f_tol && yf[4] <= _f_tol && yf[5] <= _f_tol)
  {
    // purely elastic (trial_stress, intnl_old)
    trial_stress_inadmissible = false;
    return true;
  }

  trial_stress_inadmissible = true;
  delta_dp.zero();
  returned_stress = RankTwoTensor();

  // these are the normals to the 6 yield surfaces, which are const because of the assumption of no psi hardening
  std::vector<RealVectorValue> norm(6);
  const Real sinpsi = std::sin(psi(intnl_old));
  const Real oneminus = 0.5*(1 - sinpsi);
  const Real oneplus = 0.5*(1 + sinpsi);
  norm[0](0) = oneplus; norm[0](1) = -oneminus; norm[0](2) = 0;
  norm[1](0) = -oneminus; norm[1](1) = oneplus; norm[1](2) = 0;
  norm[2](0) = oneplus; norm[2](1) = 0; norm[2](2) = -oneminus;
  norm[3](0) = -oneminus; norm[3](1) = 0; norm[3](2) = oneplus;
  norm[4](0) = 0; norm[4](1) = oneplus; norm[4](2) = -oneminus;
  norm[5](0) = 0; norm[5](1) = -oneminus; norm[5](2) = oneplus;

  // the flow directions are these norm multiplied by Eijkl.
  // I call the flow directions "n".
  // In the following I assume that the Eijkl is
  // for an isotropic situation.  Then I don't have to
  // rotate to the principal-stress frame, and i don't
  // have to worry about strange off-diagonal things
  std::vector<RealVectorValue> n(6);
  for (unsigned ys = 0; ys < 6; ++ys)
    for (unsigned i = 0; i < 3; ++i)
      for (unsigned j = 0; j < 3; ++j)
        n[ys](i) += E_ijkl(i,i,j,j)*norm[ys](j);
  const Real mag_E = E_ijkl(0, 0, 0, 0);

  // With non-zero Poisson's ratio and hardening
  // it is not computationally cheap to know whether
  // the trial stress will return to the tip, edge,
  // or plane.  The following at least
  // gives a not-completely-stupid guess
  // trial_order[0] = type of return to try first
  // trial_order[1] = type of return to try second
  // trial_order[2] = type of return to try third
  // trial_order[3] = type of return to try fourth
  // trial_order[4] = type of return to try fifth
  // In the following the "binary" stuff indicates the
  // deactive (0) and active (1) surfaces, eg
  // 110100 means that surfaces 0, 1 and 3 are active
  // and 2, 4 and 5 are deactive
  const unsigned int number_of_return_paths = 5;
  std::vector<int> trial_order(number_of_return_paths);
  if (yf[1] > _f_tol && yf[3] > _f_tol && yf[5] > _f_tol)
  {
    trial_order[0] = tip110100;
    trial_order[1] = edge010100;
    trial_order[2] = plane000100;
    trial_order[3] = edge000101;
    trial_order[4] = tip010101;
  }
  else if (yf[1] <= _f_tol && yf[3] > _f_tol && yf[5] > _f_tol)
  {
    trial_order[0] = edge000101;
    trial_order[1] = plane000100;
    trial_order[2] = tip110100;
    trial_order[3] = tip010101;
    trial_order[4] = edge010100;
  }
  else if (yf[1] <= _f_tol && yf[3] > _f_tol && yf[5] <= _f_tol)
  {
    trial_order[0] = plane000100;
    trial_order[1] = edge000101;
    trial_order[2] = edge010100;
    trial_order[3] = tip110100;
    trial_order[4] = tip010101;
  }
  else
  {
    trial_order[0] = edge010100;
    trial_order[1] = plane000100;
    trial_order[2] = edge000101;
    trial_order[3] = tip110100;
    trial_order[4] = tip010101;
  }

  unsigned trial;
  bool nr_converged = false;
  bool kt_success = false;
  std::vector<RealVectorValue> ntip(3);
  std::vector<Real> dpmtip(3);

  for (trial = 0; trial < number_of_return_paths; ++trial)
  {
    switch (trial_order[trial])
    {
      case tip110100:
        for (unsigned int i = 0; i < 3; ++i)
        {
          ntip[0](i) = n[0](i);
          ntip[1](i) = n[1](i);
          ntip[2](i) = n[3](i);
        }
        kt_success = returnTip(eigvals, ntip, dpmtip, returned_stress, intnl_old, sinphi, cohcos, 0, nr_converged, ep_plastic_tolerance, yf);
        if (nr_converged && kt_success)
        {
          dpm[0] = dpmtip[0];
          dpm[1] = dpmtip[1];
          dpm[3] = dpmtip[2];
          dpm[2] = dpm[4] = dpm[5] = 0;
        }
        break;

      case tip010101:
        for (unsigned int i = 0; i < 3; ++i)
        {
          ntip[0](i) = n[1](i);
          ntip[1](i) = n[3](i);
          ntip[2](i) = n[5](i);
        }
        kt_success = returnTip(eigvals, ntip, dpmtip, returned_stress, intnl_old, sinphi, cohcos, 0, nr_converged, ep_plastic_tolerance, yf);
        if (nr_converged && kt_success)
        {
          dpm[1] = dpmtip[0];
          dpm[3] = dpmtip[1];
          dpm[5] = dpmtip[2];
          dpm[0] = dpm[2] = dpm[4] = 0;
        }
        break;

      case edge000101:
        kt_success = returnEdge000101(eigvals, n, dpm, returned_stress, intnl_old, sinphi, cohcos, 0, mag_E, nr_converged, ep_plastic_tolerance, yf);
        break;

      case edge010100:
        kt_success = returnEdge010100(eigvals, n, dpm, returned_stress, intnl_old, sinphi, cohcos, 0, mag_E, nr_converged, ep_plastic_tolerance, yf);
        break;

      case plane000100:
        kt_success = returnPlane(eigvals, n, dpm, returned_stress, intnl_old, sinphi, cohcos, 0, nr_converged, ep_plastic_tolerance, yf);
        break;
    }

    if (nr_converged && kt_success)
      break;
  }

  if (trial == number_of_return_paths)
  {
    sinphi = std::sin(phi(intnl_old));
    cosphi = std::cos(phi(intnl_old));
    coh = cohesion(intnl_old);
    cohcos = coh*cosphi;
    yieldFunctionEigvals(eigvals[0], eigvals[1], eigvals[2], sinphi, cohcos, yf);
    Moose::err << "Trial stress = \n";
    trial_stress.print(Moose::err);
    Moose::err << "which has eigenvalues = " << eigvals[0] << " " << eigvals[1] << " " << eigvals[2] << "\n";
    Moose::err << "and yield functions = " << yf[0] << " " << yf[1] << " " << yf[2] << " " << yf[3] << " " << yf[4] << " " << yf[5] << "\n";
    Moose::err << "Internal parameter = " << intnl_old << "\n";
    mooseError("TensorMechanicsPlasticMohrCoulombMulti: FAILURE!  You probably need to implement a line search if your hardening is too severe, or you need to tune your tolerances (eg, yield_function_tolerance should be a little smaller than (young modulus)*ep_plastic_tolerance).\n");
    return false;
  }

  // success

  returned_intnl = intnl_old;
  for (unsigned i = 0; i < 6; ++i)
    returned_intnl += dpm[i];
  for (unsigned i = 0; i < 6; ++i)
    for (unsigned j = 0; j < 3; ++j)
      delta_dp(j, j) += dpm[i]*norm[i](j);
  returned_stress = eigvecs*returned_stress*(eigvecs.transpose());
  delta_dp = eigvecs*delta_dp*(eigvecs.transpose());
  return true;
}