void MultiPlasticityDebugger::fddyieldFunction_dintnl(const RankTwoTensor & stress, const std::vector<Real> & intnl, std::vector<Real> & df_dintnl) { df_dintnl.resize(_num_surfaces); std::vector<bool> act; act.assign(_num_surfaces, true); std::vector<Real> origf; yieldFunction(stress, intnl, act, origf); std::vector<Real> intnlep; intnlep.resize(_num_models); for (unsigned model = 0; model < _num_models; ++model) intnlep[model] = intnl[model]; Real ep; std::vector<Real> fep; unsigned int model; for (unsigned surface = 0; surface < _num_surfaces; ++surface) { model = modelNumber(surface); ep = _fspb_debug_intnl_change[model]; intnlep[model] += ep; yieldFunction(stress, intnlep, act, fep); df_dintnl[surface] = (fep[surface] - origf[surface]) / ep; intnlep[model] -= ep; } }
void MultiPlasticityDebugger::fddyieldFunction_dstress(const RankTwoTensor & stress, const std::vector<Real> & intnl, std::vector<RankTwoTensor> & df_dstress) { df_dstress.assign(_num_surfaces, RankTwoTensor()); std::vector<bool> act; act.assign(_num_surfaces, true); Real ep = _fspb_debug_stress_change; RankTwoTensor stressep; std::vector<Real> fep, fep_minus; for (unsigned i = 0; i < 3; ++i) for (unsigned j = 0; j < 3; ++j) { stressep = stress; // do a central difference to attempt to capture discontinuities // such as those encountered in tensile and Mohr-Coulomb stressep(i, j) += ep / 2.0; yieldFunction(stressep, intnl, act, fep); stressep(i, j) -= ep; yieldFunction(stressep, intnl, act, fep_minus); for (unsigned surface = 0; surface < _num_surfaces; ++surface) df_dstress[surface](i, j) = (fep[surface] - fep_minus[surface]) / ep; } }
void FiniteStrainPlasticBase::calculateConstraints(const RankTwoTensor & stress, const std::vector<Real> & intnl_old, const std::vector<Real> & intnl, const std::vector<Real> & pm, const RankTwoTensor & delta_dp, std::vector<Real> & f, RankTwoTensor & epp, std::vector<Real> & ic) { // yield functions yieldFunction(stress, intnl, f); // flow direction std::vector<RankTwoTensor> r; flowPotential(stress, intnl, r); epp = RankTwoTensor(); for (unsigned alpha = 0 ; alpha < numberOfYieldFunctions() ; ++alpha) epp += pm[alpha]*r[alpha]; epp -= delta_dp; // internal constraints std::vector<std::vector<Real> > h; hardPotential(stress, intnl, h); ic.resize(numberOfInternalParameters()); for (unsigned a = 0 ; a < numberOfInternalParameters() ; ++a) { ic[a] = intnl[a] - intnl_old[a]; for (unsigned alpha = 0 ; alpha < numberOfYieldFunctions() ; ++alpha) ic[a] += pm[alpha]*h[a][alpha]; } }
void TensorMechanicsPlasticModel::yieldFunctionV(const RankTwoTensor & stress, Real intnl, std::vector<Real> & f) const { f.assign(1, yieldFunction(stress, intnl)); }
void MultiPlasticityLinearSystem::calculateConstraints(const RankTwoTensor & stress, const std::vector<Real> & intnl_old, const std::vector<Real> & intnl, const std::vector<Real> & pm, const RankTwoTensor & delta_dp, std::vector<Real> & f, std::vector<RankTwoTensor> & r, RankTwoTensor & epp, std::vector<Real> & ic, const std::vector<bool> & active) { // see comments at the start of .h file mooseAssert(intnl_old.size() == _num_models, "Size of intnl_old is " << intnl_old.size() << " which is incorrect in calculateConstraints"); mooseAssert(intnl.size() == _num_models, "Size of intnl is " << intnl.size() << " which is incorrect in calculateConstraints"); mooseAssert(pm.size() == _num_surfaces, "Size of pm is " << pm.size() << " which is incorrect in calculateConstraints"); mooseAssert(active.size() == _num_surfaces, "Size of active is " << active.size() << " which is incorrect in calculateConstraints"); // yield functions yieldFunction(stress, intnl, active, f); // flow directions and "epp" flowPotential(stress, intnl, active, r); epp = RankTwoTensor(); unsigned ind = 0; for (unsigned surface = 0; surface < _num_surfaces; ++surface) if (active[surface]) epp += pm[surface] * r[ind++]; // note, even the deactivated_due_to_ld must get added in epp -= delta_dp; // internal constraints std::vector<Real> h; hardPotential(stress, intnl, active, h); ic.resize(0); ind = 0; std::vector<unsigned int> active_surfaces; std::vector<unsigned int>::iterator active_surface; for (unsigned model = 0; model < _num_models; ++model) { activeSurfaces(model, active, active_surfaces); if (active_surfaces.size() > 0) { // some surfaces are active in this model, so must form an internal constraint ic.push_back(intnl[model] - intnl_old[model]); for (active_surface = active_surfaces.begin(); active_surface != active_surfaces.end(); ++active_surface) ic[ic.size() - 1] += pm[*active_surface] * h[ind++]; // we know the correct one is h[ind] // since it was constructed in the same // manner } } }
void FiniteStrainPlasticBase::fddyieldFunction_dstress(const RankTwoTensor & stress, const std::vector<Real> & intnl, std::vector<RankTwoTensor> & df_dstress) { df_dstress.assign(numberOfYieldFunctions(), RankTwoTensor()); Real ep = _fspb_debug_stress_change; RankTwoTensor stressep; std::vector<Real> fep, fep_minus; for (unsigned i = 0 ; i < 3 ; ++i) for (unsigned j = 0 ; j < 3 ; ++j) { stressep = stress; // do a central difference to attempt to capture discontinuities // such as those encountered in tensile and Mohr-Coulomb stressep(i, j) += ep/2.0; yieldFunction(stressep, intnl, fep); stressep(i, j) -= ep; yieldFunction(stressep, intnl, fep_minus); for (unsigned alpha = 0 ; alpha < numberOfYieldFunctions() ; ++alpha) df_dstress[alpha](i, j) = (fep[alpha] - fep_minus[alpha])/ep; } }
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; }
/** * Implements the return-map algorithm via a Newton-Raphson process. * This idea is fully explained in Simo and Hughes "Computational * Inelasticity" Springer 1997, for instance, as well as many other * books on plasticity. * The basic idea is as follows. * Given: sig_old - the stress at the start of the "time step" * plastic_strain_old - the plastic strain at the start of the "time step" * eqvpstrain_old - equivalent plastic strain at the start of the "time step" * (In general we would be given some number of internal * parameters that the yield function depends upon.) * delta_d - the prescribed strain increment for this "time step" * we want to determine the following parameters at the end of this "time step": * sig - the stress * plastic_strain - the plastic strain * eqvpstrain - the equivalent plastic strain (again, in general, we would * have an arbitrary number of internal parameters). * * To determine these parameters, introduce * the "yield function", f * the "consistency parameter", flow_incr * the "flow potential", flow_dirn_ij * the "internal potential", internalPotential (in general there are as many internalPotential * functions as there are internal parameters). * All three of f, flow_dirn_ij, and internalPotential, are functions of * sig and eqvpstrain. * To find sig, plastic_strain and eqvpstrain, we need to solve the following * resid_ij = 0 * f = 0 * rep = 0 * This is done by using Newton-Raphson. * There are 8 equations here: six from resid_ij=0 (more generally there are nine * but in this case resid is symmetric); one from f=0; one from rep=0 (more generally, for N * internal parameters there are N of these equations). * * resid_ij = flow_incr*flow_dirn_ij - (plastic_strain - plastic_strain_old)_ij * = flow_incr*flow_dirn_ij - (E^{-1}(trial_stress - sig))_ij * Here trial_stress = E*(strain - plastic_strain_old) * sig = E*(strain - plastic_strain) * Note: flow_dirn_ij is evaluated at sig and eqvpstrain (not the old values). * * f is the yield function, evaluated at sig and eqvpstrain * * rep = -flow_incr*internalPotential - (eqvpstrain - eqvpstrain_old) * Here internalPotential are evaluated at sig and eqvpstrain (not the old values). * * The Newton-Raphson procedure has sig, flow_incr, and eqvpstrain as its * variables. Therefore we need the derivatives of resid_ij, f, and rep * with respect to these parameters * * In this associative J2 with isotropic hardening, things are a little more specialised. * (1) f = sqrt(3*s_ij*s_ij/2) - K(eqvpstrain) (this is called "isotropic hardening") * (2) associativity means that flow_dirn_ij = df/d(sig_ij) = s_ij*sqrt(3/2/(s_ij*s_ij)), and * this means that flow_dirn_ij*flow_dirn_ij = 3/2, so when resid_ij=0, we get * (plastic_strain_dot)_ij*(plastic_strain_dot)_ij = (3/2)*flow_incr^2, where * plastic_strain_dot = plastic_strain - plastic_strain_old * (3) The definition of equivalent plastic strain is through * eqvpstrain_dot = sqrt(2*plastic_strain_dot_ij*plastic_strain_dot_ij/3), so * using (2), we obtain eqvpstrain_dot = flow_incr, and this yields * internalPotential = -1 in the "rep" equation. */ void FiniteStrainPlasticMaterial::returnMap(const RankTwoTensor & sig_old, const Real eqvpstrain_old, const RankTwoTensor & plastic_strain_old, const RankTwoTensor & delta_d, const RankFourTensor & E_ijkl, RankTwoTensor & sig, Real & eqvpstrain, RankTwoTensor & plastic_strain) { // the yield function, must be non-positive // Newton-Raphson sets this to zero if trial stress enters inadmissible region Real f; // the consistency parameter, must be non-negative // change in plastic strain in this timestep = flow_incr*flow_potential Real flow_incr = 0.0; // direction of flow defined by the potential RankTwoTensor flow_dirn; // Newton-Raphson sets this zero // resid_ij = flow_incr*flow_dirn_ij - (plastic_strain - plastic_strain_old) RankTwoTensor resid; // Newton-Raphson sets this zero // rep = -flow_incr*internalPotential - (eqvpstrain - eqvpstrain_old) Real rep; // change in the stress (sig) in a Newton-Raphson iteration RankTwoTensor ddsig; // change in the consistency parameter in a Newton-Raphson iteration Real dflow_incr = 0.0; // change in equivalent plastic strain in one Newton-Raphson iteration Real deqvpstrain = 0.0; // convenience variable that holds the change in plastic strain incurred during the return // delta_dp = plastic_strain - plastic_strain_old // delta_dp = E^{-1}*(trial_stress - sig), where trial_stress = E*(strain - plastic_strain_old) RankTwoTensor delta_dp; // d(yieldFunction)/d(stress) RankTwoTensor df_dsig; // d(resid_ij)/d(sigma_kl) RankFourTensor dr_dsig; // dr_dsig_inv_ijkl*dr_dsig_klmn = 0.5*(de_ij de_jn + de_ij + de_jm), where de_ij = 1 if i=j, but // zero otherwise RankFourTensor dr_dsig_inv; // d(yieldFunction)/d(eqvpstrain) Real fq; // yield stress at the start of this "time step" (ie, evaluated with // eqvpstrain_old). It is held fixed during the Newton-Raphson return, // even if eqvpstrain != eqvpstrain_old. Real yield_stress; // measures of whether the Newton-Raphson process has converged Real err1, err2, err3; // number of Newton-Raphson iterations performed unsigned int iter = 0; // maximum number of Newton-Raphson iterations allowed unsigned int maxiter = 100; // plastic loading occurs if yieldFunction > toly Real toly = 1.0e-8; // Assume this strain increment does not induce any plasticity // This is the elastic-predictor sig = sig_old + E_ijkl * delta_d; // the trial stress eqvpstrain = eqvpstrain_old; plastic_strain = plastic_strain_old; yield_stress = getYieldStress(eqvpstrain); // yield stress at this equivalent plastic strain if (yieldFunction(sig, yield_stress) > toly) { // the sig just calculated is inadmissable. We must return to the yield surface. // This is done iteratively, using a Newton-Raphson process. delta_dp.zero(); sig = sig_old + E_ijkl * delta_d; // this is the elastic predictor flow_dirn = flowPotential(sig); resid = flow_dirn * flow_incr - delta_dp; // Residual 1 - refer Hughes Simo f = yieldFunction(sig, yield_stress); rep = -eqvpstrain + eqvpstrain_old - flow_incr * internalPotential(); // Residual 3 rep=0 err1 = resid.L2norm(); err2 = std::abs(f); err3 = std::abs(rep); while ((err1 > _rtol || err2 > _ftol || err3 > _eptol) && iter < maxiter) // Stress update iteration (hardness fixed) { iter++; df_dsig = dyieldFunction_dstress(sig); getJac(sig, E_ijkl, flow_incr, dr_dsig); // gets dr_dsig = d(resid_ij)/d(sig_kl) fq = dyieldFunction_dinternal(eqvpstrain); // d(f)/d(eqvpstrain) /** * The linear system is * ( dr_dsig flow_dirn 0 )( ddsig ) ( - resid ) * ( df_dsig 0 fq )( dflow_incr ) = ( - f ) * ( 0 1 -1 )( deqvpstrain ) ( - rep ) * The zeroes are: d(resid_ij)/d(eqvpstrain) = flow_dirn*d(df/d(sig_ij))/d(eqvpstrain) = 0 * and df/d(flow_dirn) = 0 (this is always true, even for general hardening and * non-associative) * and d(rep)/d(sig_ij) = -flow_incr*d(internalPotential)/d(sig_ij) = 0 */ dr_dsig_inv = dr_dsig.invSymm(); /** * Because of the zeroes and ones, the linear system is not impossible to * solve by hand. * NOTE: andy believes there was originally a sign-error in the next line. The * next line is unchanged, however andy's definition of fq is negative of * the original definition of fq. andy can't see any difference in any tests! */ dflow_incr = (f - df_dsig.doubleContraction(dr_dsig_inv * resid) + fq * rep) / (df_dsig.doubleContraction(dr_dsig_inv * flow_dirn) - fq); ddsig = dr_dsig_inv * (-resid - flow_dirn * dflow_incr); // from solving the top row of linear system, given dflow_incr deqvpstrain = rep + dflow_incr; // from solving the bottom row of linear system, given dflow_incr // update the variables flow_incr += dflow_incr; delta_dp -= E_ijkl.invSymm() * ddsig; sig += ddsig; eqvpstrain += deqvpstrain; // evaluate the RHS equations ready for next Newton-Raphson iteration flow_dirn = flowPotential(sig); resid = flow_dirn * flow_incr - delta_dp; f = yieldFunction(sig, yield_stress); rep = -eqvpstrain + eqvpstrain_old - flow_incr * internalPotential(); err1 = resid.L2norm(); err2 = std::abs(f); err3 = std::abs(rep); } if (iter >= maxiter) mooseError("Constitutive failure"); plastic_strain += delta_dp; } }
bool TensorMechanicsPlasticMeanCapTC::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; // In the following we want to solve // trial_stress - stress = E_ijkl * dpm * r ...... (1) // and either // stress.trace() = tensile_strength(intnl) ...... (2a) // intnl = intnl_old + dpm ...... (3a) // or // stress.trace() = compressive_strength(intnl) ... (2b) // intnl = intnl_old - dpm ...... (3b) // The former (2a and 3a) are chosen if // trial_stress.trace() > tensile_strength(intnl_old) // while the latter (2b and 3b) are chosen if // trial_stress.trace() < compressive_strength(intnl_old) // The variables we want to solve for are stress, dpm // and intnl. We do this using a Newton approach, starting // with stress=trial_stress and intnl=intnl_old and dpm=0 const bool tensile_failure = (trial_stress.trace() >= tensile_strength(intnl_old)); const Real dirn = (tensile_failure ? 1.0 : -1.0); RankTwoTensor n; // flow direction, which is E_ijkl * r for (unsigned i = 0; i < 3; ++i) for (unsigned j = 0; j < 3; ++j) for (unsigned k = 0; k < 3; ++k) n(i, j) += dirn * E_ijkl(i, j, k, k); const Real n_trace = n.trace(); // Perform a Newton-Raphson to find dpm when // residual = trial_stress.trace() - tensile_strength(intnl) - dpm * n.trace() [for tensile_failure=true] // or // residual = trial_stress.trace() - compressive_strength(intnl) - dpm * n.trace() [for tensile_failure=false] Real trial_trace = trial_stress.trace(); Real residual; Real jac; dpm[0] = 0; unsigned int iter = 0; do { if (tensile_failure) { residual = trial_trace - tensile_strength(intnl_old + dpm[0]) - dpm[0] * n_trace; jac = -dtensile_strength(intnl_old + dpm[0]) - n_trace; } else { residual = trial_trace - compressive_strength(intnl_old - dpm[0]) - dpm[0] * n_trace; jac = -dcompressive_strength(intnl_old - dpm[0]) - n_trace; } 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 + dirn * dpm[0]; returned_stress = trial_stress - dpm[0] * n; delta_dp = dpm[0] * dirn * returned_stress.dtrace(); return true; }
bool FiniteStrainPlasticBase::returnMap(const RankTwoTensor & stress_old, const RankTwoTensor & plastic_strain_old, const std::vector<Real> & intnl_old, const RankTwoTensor & delta_d, const RankFourTensor & E_ijkl, RankTwoTensor & stress, RankTwoTensor & plastic_strain, std::vector<Real> & intnl, std::vector<Real> & f, unsigned int & iter) { // Assume this strain increment does not induce any plasticity // This is the elastic-predictor stress = stress_old + E_ijkl * delta_d; // the trial stress plastic_strain = plastic_strain_old; for (unsigned i = 0; i < intnl_old.size() ; ++i) intnl[i] = intnl_old[i]; iter = 0; yieldFunction(stress, intnl, f); Real nr_res2 = 0; for (unsigned i = 0 ; i < f.size() ; ++i) nr_res2 += 0.5*std::pow( std::max(f[i], 0.0)/_f_tol[i], 2); if (nr_res2 < 0.5) // a purely elastic increment. // All output variables have been calculated return true; // So, from here on we know that the trial stress // is inadmissible, and we have to return from that // value to the yield surface. There are three // types of constraints we have to satisfy, listed // below, and calculated in calculateConstraints(...) // Plastic strain constraint, L2 norm must be zero (up to a tolerance) RankTwoTensor epp; // Yield function constraint passed to this function as // std::vector<Real> & f // Each yield function must be <= 0 (up to tolerance) // Internal constraint(s), must be zero (up to a tolerance) std::vector<Real> ic; // During the Newton-Raphson procedure, we'll be // changing the following parameters in order to // (attempt to) satisfy the constraints. RankTwoTensor dstress; // change in stress std::vector<Real> dpm; // change in plasticity multipliers ("consistency parameters") std::vector<Real> dintnl; // change in internal parameters // The following are used in the Newton-Raphson // Inverse of E_ijkl (assuming symmetric) RankFourTensor E_inv = E_ijkl.invSymm(); // convenience variable that holds the change in plastic strain incurred during the return // delta_dp = plastic_strain - plastic_strain_old // delta_dp = E^{-1}*(trial_stress - stress), where trial_stress = E*(strain - plastic_strain_old) RankTwoTensor delta_dp; // The "consistency parameters" (plastic multipliers) // Change in plastic strain in this timestep = pm*flowPotential // Each pm must be non-negative std::vector<Real> pm; pm.assign(numberOfYieldFunctions(), 0.0); // whether line-searching was successful bool ls_success = true; // The Newton-Raphson loops while (nr_res2 > 0.5 && iter < _max_iter && ls_success) { iter++; // calculate dstress, dpm and dintnl for one full Newton-Raphson step nrStep(stress, intnl_old, intnl, pm, E_inv, delta_dp, dstress, dpm, dintnl); // perform a line search // The line-search will exit with updated values ls_success = lineSearch(nr_res2, stress, intnl_old, intnl, pm, E_inv, delta_dp, dstress, dpm, dintnl, f, epp, ic); } if (iter >= _max_iter || !ls_success) { stress = stress_old; for (unsigned i = 0; i < intnl_old.size() ; ++i) intnl[i] = intnl_old[i]; return false; } else { plastic_strain += delta_dp; return true; } }