v0.16.3
Loading...
Searching...
No Matches
NeoHookeanLogarithmicMaterial.cpp
Go to the documentation of this file.
1/**
2 * @file NeoHookeanLogarithmicMaterial.cpp
3 * @brief Unique auxiliary logarithmic-stress inverse and its derivatives.
4 */
5
6#include <MoFEM.hpp>
7using namespace MoFEM;
8
9#include <MatrixFunction.hpp>
11#include <lapack_wrap.h>
12
13#include <algorithm>
14#include <array>
15#include <cmath>
16#include <limits>
17
18namespace EshelbianPlasticity {
19namespace {
20
21using Material = NeoHookeanLogarithmicMaterial;
22using PrincipalValues = FTensor::Tensor1<double, 3>;
23using Eigenvectors = FTensor::Tensor2<double, 3, 3>;
24
25MoFEMErrorCode validateCoordinates(const Material::Coordinates &t_values) {
27 for (int aa = 0; aa != 5; ++aa) {
28 if (!std::isfinite(t_values(aa)))
29 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
30 "Neo-Hookean logarithmic coordinates must be finite");
31 }
33}
34
35// Scale before LAPACK so its absolute tolerances also resolve very small data.
36MoFEMErrorCode evaluateEigenvectors(const Material::Coordinates &t_values,
37 PrincipalValues &t_eigenvalues,
38 Eigenvectors &t_eigenvectors,
39 double &scale) {
41 FTENSOR_INDEXES(3, i, j);
42 FTensor::Index<'A', 5> A;
43 CHKERR validateCoordinates(t_values);
44 scale = 0.;
45 for (int aa = 0; aa != 5; ++aa)
46 scale = std::max(scale, std::abs(t_values(aa)));
47 if (scale == 0.) {
48 t_eigenvalues(i) = 0.;
49 constexpr auto t_identity = FTensor::Kronecker_Delta<int>();
50 t_eigenvectors(i, j) = t_identity(i, j);
51 } else {
52 Material::Coordinates t_scaled;
53 t_scaled(A) = t_values(A) / scale;
54 t_eigenvectors(i, j) = Material::getTensor(t_scaled)(i, j);
55 CHKERR computeEigenValuesSymmetric(t_eigenvectors, t_eigenvalues);
56 t_eigenvalues(i) -=
57 t_eigenvalues(0) / 3. + t_eigenvalues(1) / 3. + t_eigenvalues(2) / 3.;
58 }
60}
61
62double getLogSum(const double a, const double b) {
63 const double maximum = std::max(a, b);
64 return maximum + std::log1p(std::exp(std::min(a, b) - maximum));
65}
66
67MoFEMErrorCode evaluateEnergy(const double mu,
68 const PrincipalValues &t_log_values,
69 double &energy) {
71 energy = 0.;
72 for (int aa = 0; aa != 3; ++aa) {
73 const double x = t_log_values(aa);
74 if (std::abs(x) <= 1.e-3) {
75 // Scale before squaring so a small strain retains its quadratic energy.
76 const double remainder =
77 0.5 + x * (1. / 6. + x * (1. / 24. + x * (1. / 120. + x / 720.)));
78 energy += ((mu / 2.) * x) * x * remainder;
79 } else if (x <= 1.) {
80 energy += (mu / 2.) * (std::expm1(x) - x);
81 } else {
82 // Combine exp(x)-1-x before exponentiating a potentially large term.
83 energy += std::exp(std::log(mu) + x - std::log(2.) +
84 std::log1p(-(1. + x) * std::exp(-x)));
85 }
86 }
87 if (!std::isfinite(energy))
88 SETERRQ(PETSC_COMM_SELF, MOFEM_OPERATION_UNSUCCESSFUL,
89 "Neo-Hookean deviatoric energy is not representable");
91}
92
93Material::SymmetricTensor getPrincipalMode(const Eigenvectors &t_vectors,
94 const int aa, const int bb) {
95 FTENSOR_INDEXES(3, i, j);
97 t_mode(i, j) = (t_vectors(aa, i) * t_vectors(bb, j) ||
98 t_vectors(bb, i) * t_vectors(aa, j)) /
99 (aa == bb ? 2. : std::sqrt(2.));
100 return t_mode;
101}
102
103MoFEMErrorCode addTangentMode(Material::Tangent &t_tangent,
104 const Material::SymmetricTensor &t_mode,
105 const double coefficient) {
107 FTensor::Index<'A', 5> A;
108 FTensor::Index<'B', 5> B;
109 if (!std::isfinite(coefficient) || coefficient <= 0.)
110 SETERRQ(PETSC_COMM_SELF, MOFEM_OPERATION_UNSUCCESSFUL,
111 "Neo-Hookean material derivative is not representable");
112 const auto t_coordinates = Material::getCoordinates(t_mode);
113 t_tangent(A, B) += coefficient * t_coordinates(A) * t_coordinates(B);
115}
116
117MoFEMErrorCode validateTangent(const Material::Tangent &t_tangent) {
119 for (int aa = 0; aa != 5; ++aa)
120 for (int bb = 0; bb != 5; ++bb)
121 if (!std::isfinite(t_tangent(aa, bb)))
122 SETERRQ(PETSC_COMM_SELF, MOFEM_OPERATION_UNSUCCESSFUL,
123 "Neo-Hookean material derivative is not representable");
125}
126
127} // namespace
128
129MoFEMErrorCode Material::validateParameters(const Parameters &parameters) {
131 if (!std::isfinite(parameters.c10) || parameters.c10 <= 0. ||
132 !std::isfinite(2. * parameters.c10) ||
133 !std::isfinite(parameters.bulkModulus) || parameters.bulkModulus <= 0.)
134 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
135 "Neo-Hookean C10 and K must be finite and positive, with finite "
136 "mu = 2*C10");
138}
139
140Material::SymmetricTensor Material::getTensor(const Coordinates &t_coordinates,
141 const double theta) {
142 return Tensor2SymmetricDeviatorBasis::getTensor(t_coordinates, theta);
143}
144
146Material::getCoordinates(const SymmetricTensor &t_tensor) {
147 return Tensor2SymmetricDeviatorBasis::getCoordinates(t_tensor);
148}
149
150MoFEMErrorCode Material::evaluateDeviator(const Parameters &parameters,
151 const Coordinates &t_deviator,
152 double &energy,
153 Coordinates *stress_ptr,
154 Tangent *hessian_ptr) {
156 FTENSOR_INDEXES(3, i, j);
157 FTensor::Index<'A', 5> A;
158 FTensor::Index<'B', 5> B;
159#ifndef NDEBUG
160 CHKERR validateParameters(parameters);
161#endif
162 const double mu = 2. * parameters.c10;
163 const double log_mu = std::log(mu);
164 PrincipalValues t_log_values;
165 Eigenvectors t_vectors;
166 double scale;
167 CHKERR evaluateEigenvectors(t_deviator, t_log_values, t_vectors, scale);
168 t_log_values(i) *= 2. * scale;
169 for (int aa = 0; aa != 3; ++aa)
170 if (!std::isfinite(t_log_values(aa)))
171 SETERRQ(PETSC_COMM_SELF, MOFEM_OPERATION_UNSUCCESSFUL,
172 "Neo-Hookean principal logarithm is not representable");
173 CHKERR evaluateEnergy(mu, t_log_values, energy);
174
175 if (stress_ptr) {
176 // Remove the spherical part before restoring the stress scale. expm1
177 // retains small differences even when all principal stretches are near one.
178 const double maximum =
179 std::max({t_log_values(0), t_log_values(1), t_log_values(2)});
180 PrincipalValues t_deviatoric_values;
181 for (int aa = 0; aa != 3; ++aa)
182 t_deviatoric_values(aa) = std::expm1(t_log_values(aa) - maximum);
183 const double mean = t_deviatoric_values(0) / 3. +
184 t_deviatoric_values(1) / 3. +
185 t_deviatoric_values(2) / 3.;
186 t_deviatoric_values(i) -= mean;
187 auto principal_stress = [log_mu, maximum](const double value) {
188 return value == 0. ? 0.
189 : std::copysign(std::exp(log_mu + maximum +
190 std::log(std::abs(value))),
191 value);
192 };
193 const auto t_stress =
194 EigenMatrix::getMat(t_deviatoric_values, t_vectors, principal_stress);
195 *stress_ptr = getCoordinates(t_stress);
196 CHKERR validateCoordinates(*stress_ptr);
197 }
198
199 if (hessian_ptr) {
200 auto &t_hessian = *hessian_ptr;
201 t_hessian(A, B) = 0.;
202 for (int aa = 0; aa != 3; ++aa) {
203 CHKERR addTangentMode(t_hessian, getPrincipalMode(t_vectors, aa, aa),
204 std::exp(std::log(2.) + log_mu + t_log_values(aa)));
205 for (int bb = 0; bb != aa; ++bb) {
206 const double difference = std::abs(t_log_values(aa) - t_log_values(bb));
207 const double factor =
208 difference == 0. ? 1. : -std::expm1(-difference) / difference;
209 const double coefficient = std::exp(
210 std::log(2.) + log_mu +
211 std::max(t_log_values(aa), t_log_values(bb)) + std::log(factor));
212 CHKERR addTangentMode(t_hessian, getPrincipalMode(t_vectors, aa, bb),
213 coefficient);
214 }
215 }
216 CHKERR validateTangent(t_hessian);
217 }
219}
220
221MoFEMErrorCode Material::evaluateVolume(const Parameters &parameters,
222 const double theta,
223 VolumeState &state) {
225#ifndef NDEBUG
226 CHKERR validateParameters(parameters);
227#endif
228 if (!std::isfinite(theta))
229 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
230 "Neo-Hookean logarithmic volume must be finite");
231 state.jacobian = std::exp(theta);
232 const double jacobian_minus_one = std::expm1(theta);
233 state.energy =
234 0.5 * (parameters.bulkModulus * jacobian_minus_one) * jacobian_minus_one;
235 const double k_j = parameters.bulkModulus * state.jacobian;
236 state.firstDerivative = k_j * jacobian_minus_one;
237 state.secondDerivative = state.firstDerivative + k_j * state.jacobian;
238 if (!std::isfinite(state.jacobian) || state.jacobian <= 0. ||
239 !std::isfinite(state.energy) || !std::isfinite(state.firstDerivative) ||
240 !std::isfinite(state.secondDerivative))
241 SETERRQ(PETSC_COMM_SELF, MOFEM_OPERATION_UNSUCCESSFUL,
242 "Neo-Hookean volumetric state is not representable");
244}
245
246MoFEMErrorCode Material::evaluateInverse(const Parameters &parameters,
247 const Coordinates &t_stress,
248 InverseState &state) {
250 FTENSOR_INDEXES(3, i, j);
251 FTensor::Index<'A', 5> A;
252 FTensor::Index<'B', 5> B;
253#ifndef NDEBUG
254 CHKERR validateParameters(parameters);
255#endif
256 const double mu = 2. * parameters.c10;
257 const double log_mu = std::log(mu);
258 PrincipalValues t_values;
259 Eigenvectors t_vectors;
260 double scale;
261 CHKERR evaluateEigenvectors(t_stress, t_values, t_vectors, scale);
262
263 // z = log(zeta/mu) retains the positive bracket even when zeta underflows.
264 const double minimum = std::min({t_values(0), t_values(1), t_values(2)});
265 PrincipalValues t_log_gaps;
266 double residual_at_zero = 0.;
267 for (int aa = 0; aa != 3; ++aa) {
268 const double difference = t_values(aa) - minimum;
269 t_log_gaps(aa) = difference == 0. || scale == 0.
270 ? -std::numeric_limits<double>::infinity()
271 : std::log(difference) + std::log(scale) - log_mu;
272 residual_at_zero += getLogSum(t_log_gaps(aa), 0.);
273 }
274 double lower = -residual_at_zero;
275 double upper = 0.;
276 double z = lower / 3.;
277 PrincipalValues t_log_values;
278 constexpr int maximum_iterations = 100;
279 bool converged = false;
280 for (int iteration = 0; iteration != maximum_iterations; ++iteration) {
281 double residual = 0.;
282 double derivative = 0.;
283 double residual_scale = 1.;
284 for (int aa = 0; aa != 3; ++aa) {
285 t_log_values(aa) = getLogSum(t_log_gaps(aa), z);
286 residual += t_log_values(aa);
287 derivative += std::exp(z - t_log_values(aa));
288 residual_scale = std::max(residual_scale, std::abs(t_log_values(aa)));
289 }
290 state.inverseResidual = residual;
291 state.inverseIterations = iteration + 1;
292 if (std::abs(residual) <=
293 32. * std::numeric_limits<double>::epsilon() * residual_scale) {
294 converged = true;
295 break;
296 }
297 if (residual > 0.)
298 upper = z;
299 else
300 lower = z;
301 const double newton = z - residual / derivative;
302 z = newton > lower && newton < upper ? newton : (lower + upper) / 2.;
303 }
304 if (!converged)
305 SETERRQ(
306 PETSC_COMM_SELF, MOFEM_OPERATION_UNSUCCESSFUL,
307 "Neo-Hookean auxiliary logarithmic-stress inverse did not converge");
308
309 const double mean_log = state.inverseResidual / 3.;
310 t_log_values(i) -= mean_log;
311 const auto t_material_deviator = EigenMatrix::getMat(
312 t_log_values, t_vectors, [](const double value) { return value / 2.; });
313 state.tMaterialDeviator = getCoordinates(t_material_deviator);
314 CHKERR evaluateEnergy(mu, t_log_values, state.deviatoricEnergy);
315 state.conjugateEnergy =
316 t_stress(A) * state.tMaterialDeviator(A) - state.deviatoricEnergy;
317 if (!std::isfinite(state.conjugateEnergy))
318 SETERRQ(PETSC_COMM_SELF, MOFEM_OPERATION_UNSUCCESSFUL,
319 "Neo-Hookean conjugate energy is not representable");
320
321 // Differentiate Td = mu*dev(exp(2*Dm)), including the scalar trace
322 // constraint. Its diagonal compliance is half the weighted variance of
323 // principal tensor increments. Merge the two smallest weights first: the
324 // within-pair variance and the variance of that pair's mean against the third
325 // principal direction give two independent modes without subtracting large
326 // matrices.
327 PrincipalValues t_log_weights;
328 t_log_weights(i) = -log_mu - t_log_values(i);
329 const double log_weight_sum = getLogSum(
330 getLogSum(t_log_weights(0), t_log_weights(1)), t_log_weights(2));
331 auto &t_compliance = state.tCompliance;
332 t_compliance(A, B) = 0.;
333 std::array<int, 3> weight_order{0, 1, 2};
334 std::sort(weight_order.begin(), weight_order.end(),
335 [&t_log_weights](const int aa, const int bb) {
336 return t_log_weights(aa) < t_log_weights(bb);
337 });
338 const int first = weight_order[0];
339 const int second = weight_order[1];
340 const int third = weight_order[2];
341 const double log_pair_sum =
342 getLogSum(t_log_weights(first), t_log_weights(second));
343 const auto t_first_mode = getPrincipalMode(t_vectors, first, first);
344 const auto t_second_mode = getPrincipalMode(t_vectors, second, second);
345 const auto t_third_mode = getPrincipalMode(t_vectors, third, third);
346 SymmetricTensor t_difference;
347 t_difference(i, j) = t_first_mode(i, j) - t_second_mode(i, j);
348 CHKERR addTangentMode(t_compliance, t_difference,
349 std::exp(-std::log(2.) + t_log_weights(first) +
350 t_log_weights(second) - log_pair_sum));
351 t_difference(i, j) =
352 std::exp(t_log_weights(first) - log_pair_sum) * t_first_mode(i, j) +
353 std::exp(t_log_weights(second) - log_pair_sum) * t_second_mode(i, j) -
354 t_third_mode(i, j);
355 CHKERR addTangentMode(t_compliance, t_difference,
356 std::exp(-std::log(2.) + log_pair_sum +
357 t_log_weights(third) - log_weight_sum));
358 for (int aa = 0; aa != 3; ++aa) {
359 for (int bb = 0; bb != aa; ++bb) {
360 const double difference = std::abs(t_log_values(aa) - t_log_values(bb));
361 const double factor =
362 difference == 0. ? 1. : difference / -std::expm1(-difference);
363 const double shear_coefficient = std::exp(
364 -std::log(2.) - log_mu -
365 std::max(t_log_values(aa), t_log_values(bb)) + std::log(factor));
366 CHKERR addTangentMode(t_compliance, getPrincipalMode(t_vectors, aa, bb),
367 shear_coefficient);
368 }
369 }
370 CHKERR validateCoordinates(state.tMaterialDeviator);
371 CHKERR validateTangent(t_compliance);
373}
374
375MoFEMErrorCode Material::evaluateFenchelGap(const Parameters &parameters,
376 const Coordinates &t_deviator,
377 const Coordinates &t_stress,
378 const InverseState &inverse,
379 double &gap,
380 double *energy_ptr) {
382 FTensor::Index<'A', 5> A;
383 CHKERR validateCoordinates(t_stress);
384 double energy;
385 CHKERR evaluateDeviator(parameters, t_deviator, energy);
386 gap = energy + inverse.conjugateEnergy - t_stress(A) * t_deviator(A);
387 if (!std::isfinite(gap))
388 SETERRQ(PETSC_COMM_SELF, MOFEM_OPERATION_UNSUCCESSFUL,
389 "Neo-Hookean Fenchel gap is not representable");
390 if (energy_ptr)
391 *energy_ptr = energy;
393}
394
395} // namespace EshelbianPlasticity
Neo-Hookean material law in orthonormal logarithmic coordinates.
#define FTENSOR_INDEXES(DIM,...)
constexpr double a
Kronecker Delta class.
#define MoFEMFunctionBegin
First executable line of each MoFEM function, used for error handling. Final line of MoFEM functions ...
@ MOFEM_OPERATION_UNSUCCESSFUL
Definition definitions.h:34
@ MOFEM_DATA_INCONSISTENCY
Definition definitions.h:31
#define MoFEMFunctionReturn(a)
Last executable line of each PETSc function used for error handling. Replaces return()
#define CHKERR
Inline error check.
FTensor::Index< 'i', SPACE_DIM > i
FTensor::Index< 'j', 3 > j
auto getMat(A &&t_val, B &&t_vec, Fun< double > f)
Get the Mat object.
PetscErrorCode MoFEMErrorCode
MoFEM/PETSc error code.
implementation of Data Operators for Forces and Sources
Definition Common.hpp:10
MoFEMErrorCode computeEigenValuesSymmetric(const MatrixDouble &mat, VectorDouble &eig, MatrixDouble &eigen_vec)
compute eigenvalues of a symmetric matrix using lapack dsyev
constexpr AssemblyType A
static MoFEMErrorCode evaluateInverse(const Parameters &parameters, const Coordinates &t_stress, InverseState &state)
Recover Dm(Td), its compliance and conjugate energy; no state is cached.
static MoFEMErrorCode validateParameters(const Parameters &parameters)
Check finite, strictly positive C10, K and representable mu = 2*C10.
static SymmetricTensor getTensor(const Coordinates &t_coordinates, double theta=0.)
Reconstruct H = D + theta*I/3 from the fixed five-coordinate basis.
static MoFEMErrorCode evaluateDeviator(const Parameters &parameters, const Coordinates &t_deviator, double &energy, Coordinates *stress_ptr=nullptr, Tangent *hessian_ptr=nullptr)
Evaluate f(D), optionally its five-component gradient and Hessian.
static MoFEMErrorCode evaluateFenchelGap(const Parameters &parameters, const Coordinates &t_deviator, const Coordinates &t_stress, const InverseState &inverse, double &gap, double *energy_ptr=nullptr)
static Coordinates getCoordinates(const SymmetricTensor &t_tensor)
Project a symmetric tensor onto the fixed trace-free basis.
static MoFEMErrorCode evaluateVolume(const Parameters &parameters, double theta, VolumeState &state)
Evaluate g(J) = K*(J-1)^2/2 and its logarithmic-volume derivatives.
double scale
Definition plastic.cpp:123