// Jacobian for stress update algorithm
void
FiniteStrainPlasticMaterial::getJac(const RankTwoTensor & sig,
                                    const RankFourTensor & E_ijkl,
                                    Real flow_incr,
                                    RankFourTensor & dresid_dsig)
{
  RankTwoTensor sig_dev, df_dsig, flow_dirn;
  RankTwoTensor dfi_dft, dfi_dsig;
  RankFourTensor dft_dsig, dfd_dft, dfd_dsig;
  Real sig_eqv;
  Real f1, f2, f3;
  RankFourTensor temp;

  sig_dev = sig.deviatoric();
  sig_eqv = getSigEqv(sig);
  df_dsig = dyieldFunction_dstress(sig);
  flow_dirn = flowPotential(sig);

  f1 = 3.0 / (2.0 * sig_eqv);
  f2 = f1 / 3.0;
  f3 = 9.0 / (4.0 * Utility::pow<3>(sig_eqv));

  dft_dsig = f1 * _deltaMixed - f2 * _deltaOuter - f3 * sig_dev.outerProduct(sig_dev);

  dfd_dsig = dft_dsig;
  dresid_dsig = E_ijkl.invSymm() + dfd_dsig * flow_incr;
}
/**
 * Get unitary flow tensor in deviatoric direction, modified Cam-Clay
 */
void
RedbackMechMaterialCC::getFlowTensor(const RankTwoTensor & sig, Real q, Real p, Real pc, RankTwoTensor & flow_tensor)
{
  if (pc > 0)
    pc *= -1;

  flow_tensor = 3.0 * sig.deviatoric() / (_slope_yield_surface * _slope_yield_surface);
  flow_tensor.addIa((2.0 * p - pc) / 3.0); //(p > 0 ? 1:-1)
                                           // TODO: do we need to normalise? If so, do we need the sqrt(3/2) factor?
  // flow_tensor /= std::pow(2.0/3.0,0.5)*flow_tensor.L2norm();
}
/**
 * Get flow tensor in deviatoric direction, modified Cam-Clay
 */
void
RedbackMechMaterialCC::getFlowTensor(const RankTwoTensor & sig,
                                     Real /*q*/,
                                     Real p,
                                     Real /*q_y*/,
                                     Real /*p_y*/,
                                     Real yield_stress,
                                     RankTwoTensor & flow_tensor)
{
  Real pc = -yield_stress;

  flow_tensor = 3.0 * sig.deviatoric() / (_slope_yield_surface * _slope_yield_surface);
  flow_tensor.addIa((2.0 * p - pc - 2*_shift_ellipse) / 3.0);
}
//Obtain derivative of flow potential w.r.t. stress (plastic flow direction)
void
FiniteStrainRatePlasticMaterial::getFlowTensor(const RankTwoTensor & sig, Real /*yield_stress*/, RankTwoTensor & flow_tensor)
{
  RankTwoTensor sig_dev;
  Real sig_eqv, val;

  sig_eqv = getSigEqv(sig);
  sig_dev = sig.deviatoric();

  val = 0.0;
  if (sig_eqv > 1e-8)
    val = 3.0 / (2.0 * sig_eqv);

  flow_tensor = sig_dev * val;
}
//Jacobian for stress update algorithm
void
FiniteStrainRatePlasticMaterial::getJac(const RankTwoTensor & sig, const RankFourTensor & E_ijkl, Real flow_incr, Real yield_stress, RankFourTensor & dresid_dsig)
{
  unsigned i, j, k ,l;
  RankTwoTensor sig_dev, flow_tensor, flow_dirn,fij;
  RankTwoTensor dfi_dft;
  RankFourTensor dft_dsig, dfd_dft, dfd_dsig, dfi_dsig;
  Real sig_eqv;
  Real f1, f2, f3;
  Real dfi_dseqv;

  sig_dev = sig.deviatoric();
  sig_eqv = getSigEqv(sig);

  getFlowTensor(sig, yield_stress, flow_tensor);

  flow_dirn = flow_tensor;
  dfi_dseqv = _ref_pe_rate * _dt * _exponent * std::pow(macaulayBracket(sig_eqv / yield_stress - 1.0), _exponent - 1.0) / yield_stress;

  for (i = 0; i < 3; ++i)
    for (j = 0; j < 3; ++j)
      for (k = 0; k < 3; ++k)
        for (l = 0; l < 3; ++l)
          dfi_dsig(i,j,k,l) = flow_dirn(i,j) * flow_dirn(k,l) * dfi_dseqv; //d_flow_increment/d_sig

  f1 = 0.0;
  f2 = 0.0;
  f3 = 0.0;

  if (sig_eqv > 1e-8)
  {
    f1 = 3.0 / (2.0 * sig_eqv);
    f2 = f1 / 3.0;
    f3 = 9.0 / (4.0 * std::pow(sig_eqv, 3.0));
  }

  for (i = 0; i < 3; ++i)
    for (j = 0; j < 3; ++j)
      for (k = 0; k < 3; ++k)
        for (l = 0; l < 3; ++l)
          dft_dsig(i,j,k,l) = f1 * deltaFunc(i,k) * deltaFunc(j,l) - f2 * deltaFunc(i,j) * deltaFunc(k,l) - f3 * sig_dev(i,j) * sig_dev(k,l); //d_flow_dirn/d_sig - 2nd part

  dfd_dsig = dft_dsig; //d_flow_dirn/d_sig
  dresid_dsig = E_ijkl.invSymm() + dfd_dsig * flow_incr + dfi_dsig; //Jacobian
}
RankFourTensor
TensorMechanicsPlasticJ2::consistentTangentOperator(const RankTwoTensor & trial_stress,
                                                    Real intnl_old,
                                                    const RankTwoTensor & stress,
                                                    Real intnl,
                                                    const RankFourTensor & E_ijkl,
                                                    const std::vector<Real> & cumulative_pm) const
{
  if (!_use_custom_cto)
    return TensorMechanicsPlasticModel::consistentTangentOperator(
        trial_stress, intnl_old, stress, intnl, E_ijkl, cumulative_pm);

  Real mu = E_ijkl(0, 1, 0, 1);

  Real h = 3 * mu + dyieldStrength(intnl);
  RankTwoTensor sij = stress.deviatoric();
  Real sII = stress.secondInvariant();
  Real equivalent_stress = std::sqrt(3.0 * sII);
  Real zeta = cumulative_pm[0] / (1.0 + 3.0 * mu * cumulative_pm[0] / equivalent_stress);

  return E_ijkl - 3.0 * mu * mu / sII / h * sij.outerProduct(sij) -
         4.0 * mu * mu * zeta * dflowPotential_dstress(stress, intnl);
}
//Jacobian for stress update algorithm
void
FiniteStrainRatePlasticMaterial::getJac(const RankTwoTensor & sig, const RankFourTensor & E_ijkl, Real flow_incr, Real yield_stress, RankFourTensor & dresid_dsig)
{
  RankTwoTensor sig_dev, flow_tensor, flow_dirn,fij;
  RankTwoTensor dfi_dft;
  RankFourTensor dfd_dft, dfd_dsig, dfi_dsig;
  Real sig_eqv;
  Real f1, f2, f3;
  Real dfi_dseqv;

  sig_dev = sig.deviatoric();
  sig_eqv = getSigEqv(sig);

  getFlowTensor(sig, yield_stress, flow_tensor);

  flow_dirn = flow_tensor;
  dfi_dseqv = _ref_pe_rate * _dt * _exponent * std::pow(macaulayBracket(sig_eqv / yield_stress - 1.0), _exponent - 1.0) / yield_stress;

  dfi_dsig = flow_dirn.outerProduct(flow_dirn) * dfi_dseqv; //d_flow_increment/d_sig

  f1 = 0.0;
  f2 = 0.0;
  f3 = 0.0;

  if (sig_eqv > 1e-8)
  {
    f1 = 3.0 / (2.0 * sig_eqv);
    f2 = f1 / 3.0;
    f3 = 9.0 / (4.0 * std::pow(sig_eqv, 3.0));
  }

  dfd_dsig = f1 * _deltaMixed - f2 * _deltaOuter - f3 * sig_dev.outerProduct(sig_dev); //d_flow_dirn/d_sig - 2nd part

  //Jacobian
  dresid_dsig = E_ijkl.invSymm() + dfd_dsig * flow_incr + dfi_dsig;
}
void
RecomputeRadialReturn::computeStress(RankTwoTensor & strain_increment,
                                     RankTwoTensor & inelastic_strain_increment,
                                     RankTwoTensor & stress_new)
{
  // Given the stretching, update the inelastic strain
  // Compute the stress in the intermediate configuration while retaining the stress history

  // compute the deviatoric trial stress and trial strain from the current intermediate configuration
  RankTwoTensor deviatoric_trial_stress = stress_new.deviatoric();

  // compute the effective trial stress
  Real dev_trial_stress_squared = deviatoric_trial_stress.doubleContraction(deviatoric_trial_stress);
  Real effective_trial_stress = std::sqrt(3.0 / 2.0 * dev_trial_stress_squared);

  //If the effective trial stress is zero, so should the inelastic strain increment be zero
  //In that case skip the entire iteration if the effective trial stress is zero
  if (effective_trial_stress != 0.0)
  {
    computeStressInitialize(effective_trial_stress);

    // Use Newton sub-iteration to determine the scalar effective inelastic strain increment
    Real scalar_effective_inelastic_strain = 0;
    unsigned int iteration = 0;
    Real residual = 10;  // use a large number here to guarantee at least one loop through while
    Real norm_residual = 10;
    Real first_norm_residual = 10;

    // create an output string with iteration information when errors occur
    std::string iteration_output;

    while (iteration < _max_its &&
          norm_residual > _absolute_tolerance &&
          (norm_residual/first_norm_residual) > _relative_tolerance)
    {
      iterationInitialize(scalar_effective_inelastic_strain);

      residual = computeResidual(effective_trial_stress, scalar_effective_inelastic_strain);
      norm_residual = std::abs(residual);
      if (iteration == 0)
      {
        first_norm_residual = norm_residual;
        if (first_norm_residual == 0)
          first_norm_residual = 1;
      }

      Real derivative = computeDerivative(effective_trial_stress, scalar_effective_inelastic_strain);

      scalar_effective_inelastic_strain -= residual / derivative;

      if (_output_iteration_info || _output_iteration_info_on_error)
      {
        iteration_output = "In the element " + Moose::stringify(_current_elem->id()) +
                         + " and the qp point " + Moose::stringify(_qp) + ": \n" +
                         + " iteration = " + Moose::stringify(iteration ) + "\n" +
                         + " effective trial stress = " + Moose::stringify(effective_trial_stress) + "\n" +
                         + " scalar effective inelastic strain = " + Moose::stringify(scalar_effective_inelastic_strain) +"\n" +
                         + " relative residual = " + Moose::stringify(norm_residual/first_norm_residual) + "\n" +
                         + " relative tolerance = " + Moose::stringify(_relative_tolerance) + "\n" +
                         + " absolute residual = " + Moose::stringify(norm_residual) + "\n" +
                         + " absolute tolerance = " + Moose::stringify(_absolute_tolerance) + "\n";
      }

      iterationFinalize(scalar_effective_inelastic_strain);
      ++iteration;
    }

    if (_output_iteration_info)
      _console << iteration_output << std::endl;

    if (iteration == _max_its &&
      norm_residual > _absolute_tolerance &&
      (norm_residual/first_norm_residual) > _relative_tolerance)
    {
      if (_output_iteration_info_on_error)
        Moose::err << iteration_output;

      mooseError("Exceeded maximum iterations in RecomputeRadialReturn solve for material: " << _name << ".  Rerun with  'output_iteration_info_on_error = true' for more information.");
    }

    // compute inelastic strain increments while avoiding a potential divide by zero
    inelastic_strain_increment = deviatoric_trial_stress;
    inelastic_strain_increment *= (3.0 / 2.0 * scalar_effective_inelastic_strain / effective_trial_stress);
  }
  else
    inelastic_strain_increment.zero();

  strain_increment -= inelastic_strain_increment;
  stress_new = _elasticity_tensor[_qp] * (strain_increment + _elastic_strain_old[_qp]);

  computeStressFinalize(inelastic_strain_increment);
}
bool
TensorMechanicsPlasticJ2::returnMap(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
{
  if (!(_use_custom_returnMap))
    return TensorMechanicsPlasticModel::returnMap(trial_stress,
                                                  intnl_old,
                                                  E_ijkl,
                                                  ep_plastic_tolerance,
                                                  returned_stress,
                                                  returned_intnl,
                                                  dpm,
                                                  delta_dp,
                                                  yf,
                                                  trial_stress_inadmissible);

  yf.resize(1);

  Real yf_orig = yieldFunction(trial_stress, intnl_old);

  yf[0] = yf_orig;

  if (yf_orig < _f_tol)
  {
    // the trial_stress is admissible
    trial_stress_inadmissible = false;
    return true;
  }

  trial_stress_inadmissible = true;
  Real mu = E_ijkl(0, 1, 0, 1);

  // Perform a Newton-Raphson to find dpm when
  // residual = 3*mu*dpm - trial_equivalent_stress + yieldStrength(intnl_old + dpm) = 0
  Real trial_equivalent_stress = yf_orig + yieldStrength(intnl_old);
  Real residual;
  Real jac;
  dpm[0] = 0;
  unsigned int iter = 0;
  do
  {
    residual = 3.0 * mu * dpm[0] - trial_equivalent_stress + yieldStrength(intnl_old + dpm[0]);
    jac = 3.0 * mu + dyieldStrength(intnl_old + dpm[0]);
    dpm[0] += -residual / jac;
    if (iter > _max_iters) // not converging
      return false;
    iter++;
  } while (residual * residual > _f_tol * _f_tol);

  // set the returned values
  yf[0] = 0;
  returned_intnl = intnl_old + dpm[0];
  RankTwoTensor nn = 1.5 * trial_stress.deviatoric() /
                     trial_equivalent_stress; // = dyieldFunction_dstress(trial_stress, intnl_old) =
                                              // the normal to the yield surface, at the trial
                                              // stress
  returned_stress = 2.0 / 3.0 * nn * yieldStrength(returned_intnl);
  returned_stress.addIa(1.0 / 3.0 * trial_stress.trace());
  delta_dp = nn * dpm[0];

  return true;
}
void
RedbackMechMaterialCC::getJac(const RankTwoTensor & sig,
                              const RankFourTensor & E_ijkl,
                              Real flow_incr,
                              Real sig_eqv,
                              Real pressure,
                              Real p_yield_stress,
                              Real q_yield_stress,
                              Real pc,
                              RankFourTensor & dresid_dsig)
{
  unsigned i, j, k, l;
  RankTwoTensor sig_dev, flow_dirn_vol, flow_dirn_dev, fij, flow_dirn, flow_tensor;
  RankTwoTensor dfi_dft;
  RankFourTensor dfd_dsig, dfi_dsig;
  Real f1, f2;
  Real dfi_dseqv_dev, dfi_dseqv_vol, dfi_dseqv;

  pc *= -1;

  sig_dev = sig.deviatoric();

  dfi_dseqv = getDerivativeFlowIncrement(sig, pressure, sig_eqv, pc, q_yield_stress, p_yield_stress);
  getFlowTensor(sig, sig_eqv, pressure, pc, flow_dirn);

  /* The following calculates the tensorial derivative (Jacobian) of the residual with respect to stress, dr_dsig
   * It consists of two terms: The first is
   * dr_dsig = (dfi_dseqv_dev*flow_dirn_dev(k,l)) * flow_dirn_dev(i,j)
   * which is the tensorial product of the flow increment tensor times the flow direction tensor
   *
   * The second is the product of the flow increment tensor times the derivative of the flow direction tensor
   * with respect to the stress tensor. See also REDBACK's documentation
   * */

  // This loop calculates the first term
  for (i = 0; i < 3; ++i)
    for (j = 0; j < 3; ++j)
      for (k = 0; k < 3; ++k)
        for (l = 0; l < 3; ++l)
          dfi_dsig(i, j, k, l) = flow_dirn(i, j) * flow_dirn(k, l) * dfi_dseqv;

  // Real flow_tensor_norm = flow_dirn.L2norm();

  // This loop calculates the second term. Read REDBACK's documentation
  // (same as J2 plasticity case)
  f1 = 0.0;
  f2 = 0.0;
  if (sig_eqv > 1e-8)
  {
    f1 = 3.0 / (_slope_yield_surface * _slope_yield_surface);
    f2 = 2.0 / 9.0 - 1.0 / (_slope_yield_surface * _slope_yield_surface);
  }
  for (i = 0; i < 3; ++i)
    for (j = 0; j < 3; ++j)
      for (k = 0; k < 3; ++k)
        for (l = 0; l < 3; ++l)
          dfd_dsig(i, j, k, l) =
            f1 * deltaFunc(i, k) * deltaFunc(j, l) -
            f2 * deltaFunc(i, j) * deltaFunc(k, l); // d_flow_dirn/d_sig - 2nd part (J2 plasticity)

  // dfd_dsig = dft_dsig1/flow_tensor_norm - 3.0 * dft_dsig2 /
  // (2*sig_eqv*flow_tensor_norm*flow_tensor_norm*flow_tensor_norm); //d_flow_dirn/d_sig
  // TODO: check if the previous two lines (i.e normalizing the flow vector) should be activated or not. Currently we
  // are using the non-unitary flow vector

  dresid_dsig = E_ijkl.invSymm() + dfd_dsig * flow_incr + dfi_dsig; // Jacobian
}
void
RedbackMechMaterialCC::getJac(const RankTwoTensor & sig,
                              const RankFourTensor & E_ijkl,
                              Real flow_incr,
                              Real sig_eqv,
                              Real pressure,
                              Real p_yield_stress,
                              Real q_yield_stress,
                              Real yield_stress,
                              RankFourTensor & dresid_dsig)
{
  unsigned i, j, k, l;
  RankTwoTensor sig_dev, flow_dirn_vol, flow_dirn_dev, fij, flow_dirn, flow_tensor;
  RankTwoTensor dfi_dft;
  RankFourTensor dfd_dsig, dfi_dsig;
  Real f1, f2, f3;
  Real dfi_dp, dfi_dseqv;

  Real pc = -yield_stress;
  sig_dev = sig.deviatoric();

  getDerivativeFlowIncrement(dfi_dp, dfi_dseqv, sig, pressure, sig_eqv, pc, q_yield_stress, p_yield_stress);
  getFlowTensor(sig, sig_eqv, pressure, q_yield_stress, p_yield_stress, yield_stress, flow_dirn);

  /* The following calculates the tensorial derivative (Jacobian) of the
   *residual with respect to stress, dr_dsig
   * It consists of two terms: The first is
   * dr_dsig = (dfi_dseqv_dev*flow_dirn_dev(k,l)) * flow_dirn_dev(i,j)
   * which is the tensorial product of the flow increment tensor times the flow
   *direction tensor
   *
   * The second is the product of the flow increment tensor times the derivative
   *of the flow direction tensor
   * with respect to the stress tensor. See also REDBACK's documentation
   * */

  f1 = 0.0;
  f2 = 0.0;
  f3 = 0.0;
  if (sig_eqv > 1e-10)
  {
    f1 = 3.0 / (_slope_yield_surface * _slope_yield_surface);
    f2 = 2.0 / 9.0 - 1.0 / (_slope_yield_surface * _slope_yield_surface);
    f3 = 3.0 / (2.0 * sig_eqv);
  }
  // This loop calculates the first term
  for (i = 0; i < 3; ++i)
    for (j = 0; j < 3; ++j)
      for (k = 0; k < 3; ++k)
        for (l = 0; l < 3; ++l)
          dfi_dsig(i, j, k, l) = flow_dirn(i, j) * (f3 * sig_dev(k, l) * dfi_dseqv + dfi_dp * deltaFunc(k, l) / 3.0);

  // This loop calculates the second term.
  for (i = 0; i < 3; ++i)
    for (j = 0; j < 3; ++j)
      for (k = 0; k < 3; ++k)
        for (l = 0; l < 3; ++l)
          dfd_dsig(i, j, k, l) = f1 * deltaFunc(i, k) * deltaFunc(j, l) + f2 * deltaFunc(i, j) * deltaFunc(k, l);

  dresid_dsig = E_ijkl.invSymm() + dfd_dsig * flow_incr + dfi_dsig; // Jacobian
}