void
TensorMechanicsPlasticMohrCoulombMulti::dyieldFunction_dintnlV(const RankTwoTensor & stress, Real intnl, std::vector<Real> & df_dintnl) const
{
  std::vector<Real> eigvals;
  stress.symmetricEigenvalues(eigvals);
  eigvals[0] += _shift;
  eigvals[2] -= _shift;

  const Real sin_angle = std::sin(phi(intnl));
  const Real cos_angle = std::cos(phi(intnl));
  const Real dsin_angle = cos_angle*dphi(intnl);
  const Real dcos_angle = -sin_angle*dphi(intnl);
  const Real dcohcos = dcohesion(intnl)*cos_angle + cohesion(intnl)*dcos_angle;

  df_dintnl.resize(6);
  df_dintnl[0] = df_dintnl[1] = 0.5*(eigvals[0] + eigvals[1])*dsin_angle - dcohcos;
  df_dintnl[2] = df_dintnl[3] = 0.5*(eigvals[0] + eigvals[2])*dsin_angle - dcohcos;
  df_dintnl[4] = df_dintnl[5] = 0.5*(eigvals[1] + eigvals[2])*dsin_angle - dcohcos;
}
Real
TensorMechanicsPlasticWeakPlaneShear::dyieldFunction_dintnl(const RankTwoTensor & stress, const Real & intnl) const
{
  return stress(2,2)*dtan_phi(intnl) - dcohesion(intnl);
}
bool
TensorMechanicsPlasticMohrCoulombMulti::returnEdge010100(const std::vector<Real> & eigvals, const std::vector<RealVectorValue> & n,
                                                         std::vector<Real> & dpm, RankTwoTensor & returned_stress, Real intnl_old,
                                                         Real & sinphi, Real & cohcos, Real initial_guess, Real mag_E,
                                                         bool & nr_converged, Real ep_plastic_tolerance, std::vector<Real> & yf) const
{
  // This returns to the Mohr-Coulomb edge
  // with 010100 being active.  This means that
  // f1=f3=0.  Denoting the returned stress by "a"
  // (in principal stress space), this means that
  // a1=a2 = (2Ccosphi + a0(1-sinphi))/(sinphi+1)
  //
  // Also, a = eigvals - dpm1*n1 - dpm3*n3,
  // where the dpm are the plastic multipliers
  // and the n are the flow directions.
  //
  // Hence we have 5 equations and 5 unknowns (a,
  // dpm1 and dpm3).  Eliminating all unknowns
  // except for x = dpm1+dpm3 gives the residual below
  // (I used mathematica)

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

  const Real numer_const = - n[1](2)*eigvals[0] - n[3](1)*eigvals[0] + n[3](2)*eigvals[0] - n[1](0)*eigvals[1] + n[1](2)*eigvals[1] + n[3](0)*eigvals[1] - n[3](2)*eigvals[1] + n[1](0)*eigvals[2] - n[3](0)*eigvals[2] + n[3](1)*eigvals[2] - n[1](1)*(-eigvals[0] + eigvals[2]);
  const Real numer_coeff1 = 2*(n[1](1) - n[1](2) - n[3](1) + n[3](2));
  const Real numer_coeff2 = n[3](2)*(-eigvals[0] - eigvals[1]) + n[1](2)*(eigvals[0] + eigvals[1]) + n[3](1)*(eigvals[0] + eigvals[2]) + (n[1](0) - n[3](0))*(eigvals[1] - eigvals[2]) - n[1](1)*(eigvals[0] + eigvals[2]);
  Real numer = numer_const + numer_coeff1*cohcos + numer_coeff2*sinphi;
  const Real denom_const = -n[1](0)*(n[3](1) - n[3](2)) + n[1](2)*(-n[3](0) + n[3](1)) + n[1](1)*(-n[3](2) + n[3](0));
  const Real denom_coeff = n[1](0)*(n[3](1) - n[3](2)) + n[1](2)*(n[3](0) + n[3](1)) - n[1](1)*(n[3](0) + n[3](2));
  Real denom = denom_const + denom_coeff*sinphi;
  Real residual = -x + numer/denom;

  Real deriv_phi;
  Real deriv_coh;
  Real jacobian;
  const Real tol = Utility::pow<2>(_f_tol / (mag_E * 10.0));
  unsigned int iter = 0;
  do {
    do {
      deriv_phi = dphi(intnl_old + dpm[3]);
      deriv_coh = dcohesion(intnl_old + dpm[3]);
      jacobian = -1 + (numer_coeff1*deriv_coh*cosphi - numer_coeff1*coh*sinphi*deriv_phi + numer_coeff2*cosphi*deriv_phi)/denom - numer*denom_coeff*cosphi*deriv_phi/denom/denom;
      x -= residual/jacobian;
      if (iter > _max_iters) // not converging
      {
        nr_converged = false;
        return false;
      }

      sinphi = std::sin(phi(intnl_old + x));
      cosphi = std::cos(phi(intnl_old + x));
      coh = cohesion(intnl_old + x);
      cohcos = coh*cosphi;
      numer = numer_const + numer_coeff1*cohcos + numer_coeff2*sinphi;
      denom = denom_const + denom_coeff*sinphi;
      residual = -x + numer/denom;
      iter ++;
    } while (residual*residual > tol);

    // now must ensure that yf[1] and yf[3] are both "zero"
    Real dpm1minusdpm3 = (2*(eigvals[1] - eigvals[2]) + x*(n[1](2) - n[1](1) + n[3](2) - n[3](1)))/(n[1](1) - n[1](2) + n[3](2) - n[3](1));
    dpm[1] = (x + dpm1minusdpm3)/2.0;
    dpm[3] = (x - dpm1minusdpm3)/2.0;

    for (unsigned i = 0; i < 3; ++i)
      returned_stress(i, i) = eigvals[i] - dpm[1]*n[1](i) - dpm[3]*n[3](i);
    yieldFunctionEigvals(returned_stress(0, 0), returned_stress(1, 1), returned_stress(2, 2), sinphi, cohcos, yf);
  } while (yf[1]*yf[1] > _f_tol*_f_tol && yf[3]*yf[3] > _f_tol*_f_tol);

  // so the NR process converged, but we must
  // check Kuhn-Tucker
  nr_converged = true;

  if (dpm[1] < -ep_plastic_tolerance || dpm[3] < -ep_plastic_tolerance)
    // Kuhn-Tucker failure.    This is a potential weak-point: if the user has set _f_tol quite large, and ep_plastic_tolerance quite small, the above NR process will quickly converge, but the solution may be wrong and violate Kuhn-Tucker
    return false;

  if (yf[0] > _f_tol || yf[2] > _f_tol || yf[4] > _f_tol || yf[5] > _f_tol)
    // Kuhn-Tucker failure
    return false;

  // success
  dpm[0] = dpm[2] = dpm[4] = dpm[5] = 0;
  return true;
}
bool
TensorMechanicsPlasticMohrCoulombMulti::returnPlane(const std::vector<Real> & eigvals, const std::vector<RealVectorValue> & n,
                                                    std::vector<Real> & dpm, RankTwoTensor & returned_stress, Real intnl_old,
                                                    Real & sinphi, Real & cohcos, Real initial_guess, bool & nr_converged,
                                                    Real ep_plastic_tolerance, std::vector<Real> & yf) const
{
  // This returns to the Mohr-Coulomb plane using n[3] (ie 000100)
  //
  // The returned point is defined by the f[3]=0 and
  //    a = eigvals - dpm[3]*n[3]
  // where "a" is the returned point and dpm[3] is the plastic multiplier.
  // This equation is a vector equation in principal stress space.
  // (Kuhn-Tucker also demands that dpm[3]>=0, but we leave checking
  // that condition for the end.)
  // Since f[3]=0, we must have
  // a[2]*(1+sinphi) + a[0]*(-1+sinphi) - 2*coh*cosphi = 0
  // which gives dpm[3] as the solution of
  //     alpha*dpm[3] + eigvals[2] - eigvals[0] + beta*sinphi - 2*coh*cosphi = 0
  // with alpha = n[3](0) - n[3](2) - (n[3](2) + n[3](0))*sinphi
  //      beta = eigvals[2] + eigvals[0]

  mooseAssert(n.size() == 6, "TensorMechanicsPlasticMohrCoulombMulti: Custom plane-return algorithm must be supplied with n of size 6, whereas yours is " << n.size());
  mooseAssert(dpm.size() == 6, "TensorMechanicsPlasticMohrCoulombMulti: Custom plane-return algorithm must be supplied with dpm of size 6, whereas yours is " << dpm.size());
  mooseAssert(yf.size() == 6, "TensorMechanicsPlasticMohrCoulombMulti: Custom tip-return algorithm must be supplied with yf of size 6, whereas yours is " << yf.size());

  dpm[3] = initial_guess;
  sinphi = std::sin(phi(intnl_old + dpm[3]));
  Real cosphi = std::cos(phi(intnl_old + dpm[3]));
  Real coh = cohesion(intnl_old + dpm[3]);
  cohcos = coh*cosphi;

  Real alpha = n[3](0) - n[3](2) - (n[3](2) + n[3](0)) * sinphi;
  Real deriv_phi;
  Real dalpha;
  const Real beta = eigvals[2] + eigvals[0];
  Real deriv_coh;

  Real residual = alpha*dpm[3] + eigvals[2] - eigvals[0] + beta * sinphi - 2.0 * cohcos; // this is 2*yf[3]
  Real jacobian;

  const Real f_tol2 =  Utility::pow<2>(_f_tol);
  unsigned int iter = 0;
  do {
    deriv_phi = dphi(intnl_old + dpm[3]);
    dalpha = -(n[3](2) + n[3](0)) * cosphi * deriv_phi;
    deriv_coh = dcohesion(intnl_old + dpm[3]);
    jacobian = alpha + dalpha * dpm[3] + beta * cosphi * deriv_phi - 2.0 * deriv_coh * cosphi + 2.0 * coh * sinphi * deriv_phi;

    dpm[3] -= residual / jacobian;
    if (iter > _max_iters) // not converging
    {
      nr_converged = false;
      return false;
    }

    sinphi = std::sin(phi(intnl_old + dpm[3]));
    cosphi = std::cos(phi(intnl_old + dpm[3]));
    coh = cohesion(intnl_old + dpm[3]);
    cohcos = coh * cosphi;
    alpha = n[3](0) - n[3](2) - (n[3](2) + n[3](0)) * sinphi;
    residual = alpha * dpm[3] + eigvals[2] - eigvals[0] + beta * sinphi - 2.0 * cohcos;
    iter++;
  } while (residual * residual > f_tol2);

  // so the NR process converged, but we must
  // check Kuhn-Tucker
  nr_converged = true;
  if (dpm[3] < -ep_plastic_tolerance)
    // Kuhn-Tucker failure
    return false;

  for (unsigned i = 0; i < 3; ++i)
    returned_stress(i, i) = eigvals[i] - dpm[3]*n[3](i);
  yieldFunctionEigvals(returned_stress(0, 0), returned_stress(1, 1), returned_stress(2, 2), sinphi, cohcos, yf);

  // by construction abs(yf[3]) = abs(residual/2) < _f_tol/2
  if (yf[0] > _f_tol || yf[1] > _f_tol || yf[2] > _f_tol || yf[4] > _f_tol || yf[5] > _f_tol)
    // Kuhn-Tucker failure
    return false;

  // success!
  dpm[0] = dpm[1] = dpm[2] = dpm[4] = dpm[5] = 0;
  return true;
}
bool
TensorMechanicsPlasticMohrCoulombMulti::returnTip(const std::vector<Real> & eigvals, const std::vector<RealVectorValue> & n,
                                                  std::vector<Real> & dpm, RankTwoTensor & returned_stress, Real intnl_old,
                                                  Real & sinphi, Real & cohcos, Real initial_guess, bool & nr_converged,
                                                  Real ep_plastic_tolerance, std::vector<Real> & yf) const
{
  // This returns to the Mohr-Coulomb tip using the THREE directions
  // given in n, and yields the THREE dpm values.  Note that you
  // must supply THREE suitable n vectors out of the total of SIX
  // flow directions, and then interpret the THREE dpm values appropriately.
  //
  // Eg1.  You supply the flow directions n[0], n[1] and n[3] as
  //       the "n" vectors.  This is return-to-the-tip via 110100.
  //       Then the three returned dpm values will be dpm[0], dpm[1] and dpm[3].

  // Eg2.  You supply the flow directions n[1], n[3] and n[5] as
  //       the "n" vectors.  This is return-to-the-tip via 010101.
  //       Then the three returned dpm values will be dpm[1], dpm[3] and dpm[5].

  // The returned point is defined by the three yield functions (corresonding
  // to the three supplied flow directions) all being zero.
  // that is, returned_stress = diag(cohcot, cohcot, cohcot), where
  // cohcot = cohesion*cosphi/sinphi
  // where intnl = intnl_old + dpm[0] + dpm[1] + dpm[2]
  // The 3 plastic multipliers, dpm, are defiend by the normality condition
  //     eigvals - cohcot = dpm[0]*n[0] + dpm[1]*n[1] + dpm[2]*n[2]
  // (Kuhn-Tucker demands that all dpm are non-negative, but we leave
  //  that checking for the end.)
  // (Remember that these "n" vectors and "dpm" values must be interpreted
  //  differently depending on what you pass into this function.)
  // This is a vector equation with solution (A):
  //   dpm[0] = triple(eigvals - cohcot, n[1], n[2])/trip;
  //   dpm[1] = triple(eigvals - cohcot, n[2], n[0])/trip;
  //   dpm[2] = triple(eigvals - cohcot, n[0], n[1])/trip;
  // where trip = triple(n[0], n[1], n[2]).
  // By adding the three components of that solution together
  // we can get an equation for x = dpm[0] + dpm[1] + dpm[2],
  // and then our Newton-Raphson only involves one variable (x).
  // In the following, i specialise to the isotropic situation.

  mooseAssert(n.size() == 3, "TensorMechanicsPlasticMohrCoulombMulti: Custom tip-return algorithm must be supplied with n of size 3, whereas yours is " << n.size());
  mooseAssert(dpm.size() == 3, "TensorMechanicsPlasticMohrCoulombMulti: Custom tip-return algorithm must be supplied with dpm of size 3, whereas yours is " << dpm.size());
  mooseAssert(yf.size() == 6, "TensorMechanicsPlasticMohrCoulombMulti: Custom tip-return algorithm must be supplied with yf of size 6, whereas yours is " << yf.size());

  Real x = initial_guess;
  const Real trip = triple_product(n[0], n[1], n[2]);
  sinphi = std::sin(phi(intnl_old + x));
  Real cosphi = std::cos(phi(intnl_old + x));
  Real coh = cohesion(intnl_old + x);
  cohcos = coh*cosphi;
  Real cohcot = cohcos/sinphi;

  if (_cohesion.modelName().compare("Constant") != 0 || _phi.modelName().compare("Constant") != 0)
  {
    // Finding x is expensive.  Therefore
    // although x!=0 for Constant Hardening, solution (A)
    // demonstrates that we don't
    // actually need to know x to find the dpm for
    // Constant Hardening.
    //
    // However, for nontrivial Hardening, the following
    // is necessary
    // cohcot_coeff = [1,1,1].(Cross[n[1], n[2]] + Cross[n[2], n[0]] + Cross[n[0], n[1]])/trip
    Real cohcot_coeff = (n[0](0)*(n[1](1) - n[1](2) - n[2](1)) + (n[1](2) - n[1](1))*n[2](0) + (n[1](0) - n[1](2))*n[2](1) + n[0](2)*(n[1](0) - n[1](1) - n[2](0) + n[2](1)) + n[0](1)*(n[1](2) - n[1](0) + n[2](0) - n[2](2)) + (n[0](0) - n[1](0) + n[1](1))*n[2](2))/trip;
    // eig_term = eigvals.(Cross[n[1], n[2]] + Cross[n[2], n[0]] + Cross[n[0], n[1]])/trip
    Real eig_term = eigvals[0]*(-n[0](2)*n[1](1) + n[0](1)*n[1](2) + n[0](2)*n[2](1) - n[1](2)*n[2](1) - n[0](1)*n[2](2) + n[1](1)*n[2](2))/trip;
    eig_term += eigvals[1]*(n[0](2)*n[1](0) - n[0](0)*n[1](2) - n[0](2)*n[2](0) + n[1](2)*n[2](0) + n[0](0)*n[2](2) - n[1](0)*n[2](2))/trip;
    eig_term += eigvals[2]*(n[0](0)*n[1](1) - n[1](1)*n[2](0) + n[0](1)*n[2](0) - n[0](1)*n[1](0) - n[0](0)*n[2](1) + n[1](0)*n[2](1))/trip;
    // and finally, the equation we want to solve is:
    // x - eig_term + cohcot*cohcot_coeff = 0
    // but i divide by cohcot_coeff so the result has the units of
    // stress, so using _f_tol as a convergence check is reasonable
    eig_term /= cohcot_coeff;
    Real residual = x/cohcot_coeff - eig_term + cohcot;
    Real jacobian;
    Real deriv_phi;
    Real deriv_coh;
    unsigned int iter = 0;
    do {
      deriv_phi = dphi(intnl_old + x);
      deriv_coh = dcohesion(intnl_old + x);
      jacobian = 1.0/cohcot_coeff + deriv_coh*cosphi/sinphi - coh*deriv_phi/Utility::pow<2>(sinphi);
      x += -residual/jacobian;

      if (iter > _max_iters) // not converging
      {
        nr_converged = false;
        return false;
      }

      sinphi = std::sin(phi(intnl_old + x));
      cosphi = std::cos(phi(intnl_old + x));
      coh = cohesion(intnl_old + x);
      cohcos = coh*cosphi;
      cohcot = cohcos/sinphi;
      residual = x/cohcot_coeff - eig_term + cohcot;
      iter ++;
    } while (residual*residual > _f_tol*_f_tol/100);
  }

  // so the NR process converged, but we must
  // calculate the individual dpm values and
  // check Kuhn-Tucker
  nr_converged = true;
  if (x < -3*ep_plastic_tolerance)
    // obviously at least one of the dpm are < -ep_plastic_tolerance.  No point in proceeding.  This is a potential weak-point: if the user has set _f_tol quite large, and ep_plastic_tolerance quite small, the above NR process will quickly converge, but the solution may be wrong and violate Kuhn-Tucker.
    return false;

  // The following is the solution (A) written above
  // (dpm[0] = triple(eigvals - cohcot, n[1], n[2])/trip, etc)
  // in the isotropic situation
  RealVectorValue v;
  v(0) = eigvals[0] - cohcot;
  v(1) = eigvals[1] - cohcot;
  v(2) = eigvals[2] - cohcot;
  dpm[0] = triple_product(v, n[1], n[2])/trip;
  dpm[1] = triple_product(v, n[2], n[0])/trip;
  dpm[2] = triple_product(v, n[0], n[1])/trip;

  if (dpm[0] < -ep_plastic_tolerance || dpm[1] < -ep_plastic_tolerance || dpm[2] < -ep_plastic_tolerance)
    // Kuhn-Tucker failure.  No point in proceeding
    return false;

  // Kuhn-Tucker has succeeded: just need returned_stress and yf values
  // I do not use the dpm to calculate returned_stress, because that
  // might add error (and computational effort), simply:
  returned_stress(0, 0) = returned_stress(1, 1) = returned_stress(2, 2) = cohcot;
  // So by construction the yield functions are all zero
  yf[0] = yf[1] = yf[2] = yf[3] = yf[4] = yf[5] = 0;
  return true;
}