Real TensorMechanicsPlasticMeanCapTC::yieldFunction(const RankTwoTensor & stress, Real intnl) const { const Real tr = stress.trace(); const Real t_str = tensile_strength(intnl); if (tr >= t_str) return tr - t_str; const Real c_str = compressive_strength(intnl); if (tr <= c_str) return -(tr - c_str); // the following is zero at tr = t_str, and at tr = c_str // it also has derivative = 1 at tr = t_str, and derivative = -1 at tr = c_str // it also has second derivative = 0, at these points. // This makes the complete yield function C2 continuous. return (c_str - t_str) / M_PI * std::sin(M_PI * (tr - c_str) / (t_str - c_str)); }
void TensorMechanicsPlasticMeanCapTC::activeConstraints(const std::vector<Real> & f, const RankTwoTensor & stress, Real intnl, const RankFourTensor & Eijkl, std::vector<bool> & act, RankTwoTensor & returned_stress) const { act.assign(1, false); if (f[0] <= _f_tol) { returned_stress = stress; return; } const Real tr = stress.trace(); const Real t_str = tensile_strength(intnl); Real str; Real dirn; if (tr >= t_str) { str = t_str; dirn = 1.0; } else { str = compressive_strength(intnl); dirn = -1.0; } RankTwoTensor n; // flow direction 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 * Eijkl(i, j, k, k); // returned_stress = stress - gamma*n // and taking the trace of this and using // Tr(returned_stress) = str, gives // gamma = (Tr(stress) - str)/Tr(n) Real gamma = (stress.trace() - str) / n.trace(); for (unsigned i = 0; i < 3; ++i) for (unsigned j = 0; j < 3; ++j) returned_stress(i, j) = stress(i, j) - gamma * n(i, j); act[0] = true; }
Real TensorMechanicsPlasticTensile::yieldFunction(const RankTwoTensor & stress, const Real & intnl) const { Real mean_stress = stress.trace()/3.0; Real sin3Lode = stress.sin3Lode(_lode_cutoff, 0); if (sin3Lode <= _sin3tt) { // the non-edge-smoothed version std::vector<Real> eigvals; stress.symmetricEigenvalues(eigvals); return mean_stress + std::sqrt(_small_smoother2 + std::pow(eigvals[2] - mean_stress, 2)) - tensile_strength(intnl); } else { // the edge-smoothed version Real kk = _aaa + _bbb*sin3Lode + _ccc*std::pow(sin3Lode, 2); Real sibar2 = stress.secondInvariant(); return mean_stress + std::sqrt(_small_smoother2 + sibar2*std::pow(kk, 2)) - tensile_strength(intnl); } }
bool TensorMechanicsPlasticTensileMulti::returnEdge(const std::vector<Real> & eigvals, const std::vector<std::vector<Real> > & n, std::vector<Real> & dpm, RankTwoTensor & returned_stress, const Real & intnl_old, const Real & initial_guess) const { // work out the point to which we would return, "a". It is defined by // f1 = 0 = f2, and the normality condition: // (eigvals - a).(n1 x n2) = 0, // where eigvals is the starting position // (it is a vector in principal stress space). // To get f1=0=f2, we need a = (a0, str, str), and a0 is found // by expanding the normality condition to yield: // a0 = (-str*n1xn2[1] - str*n1xn2[2] + edotn1xn2)/n1xn2[0]; // where edotn1xn2 = eigvals.(n1 x n2) // // We need to find the plastic multipliers, dpm, defined by // eigvals - a = dpm[1]*n1 + dpm[2]*n2 // For the isotropic case, and defining eminusa = eigvals - a, // the solution is easy: // dpm[0] = 0; // dpm[1] = (eminusa[1] - eminusa[0])/(n[1][1] - n[1][0]); // dpm[2] = (eminusa[2] - eminusa[0])/(n[2][2] - n[2][0]); // // Now specialise to the isotropic case. Define // x = dpm[1] + dpm[2] = (eigvals[1] + eigvals[2] - 2*str)/(n[0][0] + n[0][1]) // Notice that the RHS is a function of x, so we solve using // Newton-Raphson starting with x=initial_guess Real x = initial_guess; Real denom = n[0][0] + n[0][1]; Real str = tensile_strength(intnl_old + x); if (_strength.modelName().compare("Constant") != 0) { // Finding x is expensive. Therefore // although x!=0 for Constant Hardening, solution // for dpm above 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 Real eig = eigvals[1] + eigvals[2]; Real residual = denom*x - eig + 2*str; Real jacobian; unsigned int iter = 0; do { jacobian = denom + 2*dtensile_strength(intnl_old + x); x += -residual/jacobian; if (iter > _max_iters) // not converging return false; str = tensile_strength(intnl_old + x); residual = denom*x - eig + 2*str; iter ++; } while (residual*residual > _f_tol*_f_tol); } dpm[0] = 0; dpm[1] = ((eigvals[1]*n[0][0] - eigvals[2]*n[0][1])/(n[0][0] - n[0][1]) - str)/denom; dpm[2] = ((eigvals[2]*n[0][0] - eigvals[1]*n[0][1])/(n[0][0] - n[0][1]) - str)/denom; returned_stress(0, 0) = eigvals[0] - n[0][1]*(dpm[1] + dpm[2]); returned_stress(1, 1) = returned_stress(2, 2) = str; return true; }
bool TensorMechanicsPlasticTensileMulti::returnTip(const std::vector<Real> & eigvals, const std::vector<std::vector<Real> > & n, std::vector<Real> & dpm, RankTwoTensor & returned_stress, const Real & intnl_old, const Real & initial_guess) const { // The returned point is defined by f0=f1=f2=0. // that is, returned_stress = diag(str, str, str), where // str = tensile_strength(intnl), // where intnl = intnl_old + dpm[0] + dpm[1] + dpm[2] // The 3 plastic multipliers, dpm, are defiend by the normality condition // eigvals - str = 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 later.) // This is a vector equation with solution (A): // dpm[0] = triple(eigvals - str, n[1], n[2])/trip; // dpm[1] = triple(eigvals - str, n[2], n[0])/trip; // dpm[2] = triple(eigvals - str, 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. Real x = initial_guess; Real denom = (n[0][0] - n[0][1])*(n[0][0] + 2*n[0][1]); Real str = tensile_strength(intnl_old + x); if (_strength.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 Real eig = eigvals[0] + eigvals[1] + eigvals[2]; Real bul = (n[0][0] + 2*n[0][1]); // and finally, the equation we want to solve is: // bul*x - eig + 3*str = 0 // where str=tensile_strength(intnl_old + x) // and x = dpm[0] + dpm[1] + dpm[2] // (Note this has units of stress, so using _f_tol as a convergence check is reasonable.) // Use Netwon-Raphson with initial guess x = 0 Real residual = bul*x - eig + 3*str; Real jacobian; unsigned int iter = 0; do { jacobian = bul + 3*dtensile_strength(intnl_old + x); x += -residual/jacobian; if (iter > _max_iters) // not converging return false; str = tensile_strength(intnl_old + x); residual = bul*x - eig + 3*str; iter ++; } while (residual*residual > _f_tol*_f_tol); } // The following is the solution (A) written above // (dpm[0] = triple(eigvals - str, n[1], n[2])/trip, etc) // in the isotropic situation dpm[0] = (n[0][0]*(eigvals[0] - str) + n[0][1]*(eigvals[0] - eigvals[1] - eigvals[2] + str))/denom; dpm[1] = (n[0][0]*(eigvals[1] - str) + n[0][1]*(eigvals[1] - eigvals[2] - eigvals[0] + str))/denom; dpm[2] = (n[0][0]*(eigvals[2] - str) + n[0][1]*(eigvals[2] - eigvals[0] - eigvals[1] + str))/denom; returned_stress(0, 0) = returned_stress(1, 1) = returned_stress(2, 2) = str; return true; }
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 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; }
Real TensorMechanicsPlasticWeakPlaneTensile::yieldFunction(const RankTwoTensor & stress, const Real & intnl) const { return stress(2,2) - tensile_strength(intnl); }
void TensorMechanicsPlasticTensileMulti::activeConstraints(const std::vector<Real> & f, const RankTwoTensor & stress, const Real & intnl, const RankFourTensor & Eijkl, std::vector<bool> & act, RankTwoTensor & returned_stress) const { act.assign(3, false); if (f[0] <= _f_tol && f[1] <= _f_tol && f[2] <= _f_tol) { returned_stress = stress; return; } returned_stress = RankTwoTensor(); std::vector<Real> eigvals; RankTwoTensor eigvecs; stress.symmetricEigenvaluesEigenvectors(eigvals, eigvecs); eigvals[0] += _shift; eigvals[2] -= _shift; Real str = tensile_strength(intnl); std::vector<Real> v(3); v[0] = eigvals[0] - str; v[1] = eigvals[1] - str; v[2] = eigvals[2] - str; // these are the normals to the 3 yield surfaces std::vector<std::vector<Real> > n(3); n[0].resize(3); n[0][0] = 1 ; n[0][1] = 0 ; n[0][2] = 0; n[1].resize(3); n[1][0] = 0 ; n[1][1] = 1 ; n[1][2] = 0; n[2].resize(3); n[2][0] = 0 ; n[2][1] = 0 ; n[2][2] = 1; // the flow directions are these n multiplied by Eijkl. // I re-use the name "n" for the flow directions // In the following I assume that the Eijkl is // for an isotropic situation. This is the most // common when using TensileMulti, and remember // that the returned_stress need not be perfect // anyway. // I divide by E(0,0,0,0) so the n remain of order 1 Real ratio = Eijkl(1,1,0,0)/Eijkl(0,0,0,0); n[0][1] = n[0][2] = ratio; n[1][0] = n[1][2] = ratio; n[2][0] = n[2][1] = ratio; // 111 (tip) // For tip-return to satisfy Kuhn-Tucker, we need // v = alpha*n[0] + beta*n[1] * gamma*n[2] // with alpha, beta, and gamma all being non-negative (they are // the plasticity multipliers) Real denom = triple(n[0], n[1], n[2]); if (triple(v, n[0], n[1])/denom >= 0 && triple(v, n[1], n[2])/denom >= 0 && triple(v, n[2], n[0])/denom >= 0) { act[0] = act[1] = act[2] = true; returned_stress(0, 0) = returned_stress(1, 1) = returned_stress(2, 2) = str; returned_stress = eigvecs*returned_stress*(eigvecs.transpose()); return; } // 011 (edge) std::vector<Real> n1xn2(3); n1xn2[0] = n[1][1]*n[2][2] - n[1][2]*n[2][1]; n1xn2[1] = n[1][2]*n[2][0] - n[1][0]*n[2][2]; n1xn2[2] = n[1][0]*n[2][1] - n[1][1]*n[2][0]; // work out the point to which we would return, "a". It is defined by // f1 = 0 = f2, and that (p - a).(n1 x n2) = 0, where "p" is the // starting position (p = eigvals). // In the following a = (a0, str, str) Real pdotn1xn2 = dot(eigvals, n1xn2); Real a0 = (-str*n1xn2[1] - str*n1xn2[2] + pdotn1xn2)/n1xn2[0]; // we need p - a = alpha*n1 + beta*n2, where alpha and beta are non-negative // for Kuhn-Tucker to be satisfied std::vector<Real> pminusa(3); pminusa[0] = eigvals[0] - a0; pminusa[1] = v[1]; pminusa[2] = v[2]; if ((pminusa[2] - pminusa[0])/(1.0 - ratio) >= 0 && (pminusa[1] - pminusa[0])/(1.0 - ratio) >= 0) { returned_stress(0, 0) = a0; returned_stress(1, 1) = str; returned_stress(2, 2) = str; returned_stress = eigvecs*returned_stress*(eigvecs.transpose()); act[1] = act[2] = true; return; } // 001 (plane) // the returned point, "a", is defined by f2=0 and // a = p - alpha*n2 Real alpha = (eigvals[2] - str)/n[2][2]; act[2] = true; returned_stress(0, 0) = eigvals[0] - alpha*n[2][0]; returned_stress(1, 1) = eigvals[1] - alpha*n[2][1]; returned_stress(2, 2) = str; returned_stress = eigvecs*returned_stress*(eigvecs.transpose()); return; }