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; }