v0.16.0
Loading...
Searching...
No Matches
HMHNeohookean.cpp
Go to the documentation of this file.
1/**
2 * @file HMHNeohookean.cpp
3 * @brief Abaqus-compatible compressible Neo-Hookean material in principal
4 * stretches
5 * @date 2024-08-31
6 *
7 * @copyright Copyright (c) 2024
8 *
9 * Constitutive synopsis
10 * ---------------------
11 * The deformation gradient is split as F = R U, where R is a rigid rotation
12 * and U is the symmetric positive-definite stretch tensor. If lambda_a are
13 * the eigenvalues (principal stretches) of U, this implementation evaluates
14 *
15 * W(U) = c10 (J^{-2/3} tr(U^2) - 3) + K/2 (J - 1)^2,
16 * J = det(U), mu_0 = 2 c10, D_1 = 2/K.
17 *
18 * The internal scalar variable is the natural Hencky stretch
19 * h_a=log(lambda_a), so lambda_a=exp(h_a).
20 *
21 * This is the same reduced-polynomial N=1 potential used by
22 * mofem/src/materials/impl/MatNeohookean.cpp. Require c10 > 0 and K > 0. The
23 * energy is objective, so rigid rotations do not change it: zero rotational
24 * modes in a full F-based Hessian are expected and are not a loss of stretch
25 * stability.
26 */
27
28#include <Lie.hpp>
29#include <algorithm>
30#include <cmath>
31#include <cstdio>
32#include <iomanip>
33#include <limits>
34#include <sstream>
35#include <vector>
36
37namespace EshelbianPlasticity {
38
40
41 // Abaqus-compatible compressible Neo-Hookean model
42 //
43 // W(U) = c10 (J^(-2/3) tr(U^2) - 3) + K/2 (J - 1)^2,
44 // mu_0 = 2 c10, D_1 = 2/K, J = det(U).
45
46 static inline double getShearModulus(const double c10) { return 2. * c10; }
47
48 static inline double getAbaqusD1(const double K) { return 2. / K; }
49
57
58 static MoFEMErrorCode
59 getCoordinateStretchFromStretch(const double stretch,
60 double &coordinate_stretch) {
62 if (!std::isfinite(stretch) || stretch <= 0.) {
63 SETERRQ(PETSC_COMM_SELF, MOFEM_INVALID_DATA,
64 "Abaqus Neo-Hookean stretch must be finite and positive");
65 }
66 coordinate_stretch = std::log(stretch);
67 if (!std::isfinite(coordinate_stretch)) {
68 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FP,
69 "Non-finite Abaqus Neo-Hookean coordinate stretch");
70 }
72 }
73
74 template <typename T>
75 static inline double getLogJacobian(T &principal_coordinate_stretches) {
77 constexpr auto t_one = FTensor::One<>();
78 return principal_coordinate_stretches(i) * t_one(i);
79 }
80
81 template <typename T>
82 static inline PrincipalState
83 getPrincipalState(const double K, T &principal_coordinate_stretches) {
85 constexpr auto t_one = FTensor::One<>();
86 const double log_jacobian = getLogJacobian(principal_coordinate_stretches);
87 const double jacobian = std::exp(log_jacobian);
88 const double jacobian_to_minus_two_thirds =
89 std::exp((-2. / 3.) * log_jacobian);
90 FTensor::Tensor1<double, SPACE_DIM> t_squared_stretches;
91 for (int aa = 0; aa != SPACE_DIM; ++aa) {
92 t_squared_stretches(aa) =
93 std::exp(2. * principal_coordinate_stretches(aa));
94 }
95 const double first_invariant = t_squared_stretches(i) * t_one(i);
96 return {jacobian, jacobian_to_minus_two_thirds, first_invariant,
97 K * jacobian * (jacobian - 1.),
98 K * jacobian * (2. * jacobian - 1.)};
99 }
100
101 static MoFEMErrorCode validatePrincipalState(const PrincipalState &state,
102 const char *source) {
104 if (!std::isfinite(state.jacobian) || state.jacobian <= 0. ||
105 !std::isfinite(state.jacobianToMinusTwoThirds) ||
106 state.jacobianToMinusTwoThirds <= 0. ||
107 !std::isfinite(state.firstInvariant) || state.firstInvariant <= 0. ||
108 !std::isfinite(state.volumetricStress) ||
109 !std::isfinite(state.volumetricTangent)) {
110 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FP,
111 "Non-finite Abaqus Neo-Hookean principal state in %s", source);
112 }
114 }
115
116 static inline double getPrincipalCoordinateStress(const double c10,
117 const PrincipalState &state,
118 const double v) {
119 const double squared_stretch = std::exp(2. * v);
120 return getShearModulus(c10) * state.jacobianToMinusTwoThirds *
121 (squared_stretch - state.firstInvariant / 3.) +
122 state.volumetricStress;
123 }
124
126 const double c10, const PrincipalState &state, const double v) {
127 return 2. * getShearModulus(c10) * state.jacobianToMinusTwoThirds *
128 std::exp(2. * v);
129 }
130
131 template <typename T>
132 static inline double getStrainEnergy(const double c10, const double K,
133 T &principal_coordinate_stretches) {
134 const auto state = getPrincipalState(K, principal_coordinate_stretches);
135 const double jacobian_minus_one = state.jacobian - 1.;
136 return c10 * (state.jacobianToMinusTwoThirds * state.firstInvariant - 3.) +
137 0.5 * K * jacobian_minus_one * jacobian_minus_one;
138 }
139
140 static MoFEMErrorCode validateMaterialParameters(const double c10,
141 const double K,
142 const char *source) {
144 if (!std::isfinite(c10) || c10 <= 0.) {
145 SETERRQ(PETSC_COMM_SELF, MOFEM_INVALID_DATA,
146 "Neo-Hookean c10 must be finite and positive in %s", source);
147 }
148 if (!std::isfinite(K) || K <= 0.) {
149 SETERRQ(PETSC_COMM_SELF, MOFEM_INVALID_DATA,
150 "Abaqus Neo-Hookean K must be finite and positive in %s "
151 "(received c10=%.16g, K=%.16g)",
152 source, c10, K);
153 }
155 }
156
157 HMHNeohookean(MoFEM::Interface &m_field, const double c10, const double K)
158 : PhysicalEquations(), mField(m_field), c10_default(c10), K_default(K) {
159
160 CHK_THROW_MESSAGE(getOptions(), "get options failed");
162 "extract block data failed");
163
166 }
167
170 "Neo-Hookean requires natural logarithmic stretch");
171 }
172 }
173
174 auto getMaterialParameters(EntityHandle ent) {
175 for (auto &b : blockData) {
176 if (b.blockEnts.find(ent) != b.blockEnts.end()) {
177 return std::make_pair(b.c10, b.K);
178 }
179 }
180 if (blockData.size() != 0)
182 "Block not found for entity handle. If you mat set "
183 "block, set it to all elements");
184 return std::make_pair(c10_default, K_default);
185 }
186
187 struct OpJacobian : public EshelbianPlasticity::OpJacobian {
188 using EshelbianPlasticity::OpJacobian::OpJacobian;
189 MoFEMErrorCode evaluateRhs(EntData &data) { return 0; }
190 MoFEMErrorCode evaluateLhs(EntData &data) { return 0; }
191 };
192
194 returnOpJacobian(const bool eval_rhs, const bool eval_lhs,
195 boost::shared_ptr<DataAtIntegrationPts> data_ptr,
196 boost::shared_ptr<PhysicalEquations> physics_ptr) {
197 return (new OpJacobian(eval_rhs, eval_lhs, data_ptr, physics_ptr));
198 }
199
200 MoFEMErrorCode getOptions() {
202 PetscOptionsBegin(PETSC_COMM_WORLD, "neo_hookean_", "", "none");
203
204 CHKERR PetscOptionsScalar("-c10", "C10", "", c10_default, &c10_default,
205 PETSC_NULLPTR);
206 CHKERR PetscOptionsScalar("-K", "Bulk modulus K", "", K_default, &K_default,
207 PETSC_NULLPTR);
208
209 alphaGradU = 0;
210 CHKERR PetscOptionsScalar("-viscosity_alpha_grad_u", "viscosity", "",
211 alphaGradU, &alphaGradU, PETSC_NULLPTR);
212 PetscOptionsEnd();
213
215 "default options");
216
217 MOFEM_LOG_CHANNEL("WORLD");
218 MOFEM_TAG_AND_LOG("WORLD", Sev::inform, "MatBlock Neo-Hookean (default)")
219 << " c10 = " << c10_default << " K = " << K_default
220 << " mu = " << getShearModulus(c10_default)
221 << " Abaqus D1 = " << getAbaqusD1(K_default)
222 << " grad alpha u = " << alphaGradU;
224 }
225
226 MoFEMErrorCode extractBlockData(Sev sev) {
227 return extractBlockData(
228
229 mField.getInterface<MeshsetsManager>()->getCubitMeshsetPtr(std::regex(
230
231 (boost::format("%s(.*)") % "MAT_NEOHOOKEAN").str()
232
233 )),
234
235 sev);
236 }
237
238 MoFEMErrorCode
239 extractBlockData(std::vector<const CubitMeshSets *> meshset_vec_ptr,
240 Sev sev) {
242
243 for (auto m : meshset_vec_ptr) {
244 MOFEM_LOG("EP", sev) << *m;
245 std::vector<double> block_data;
246 CHKERR m->getAttributes(block_data);
247 auto get_block_ents = [&]() {
248 Range ents;
249 CHKERR mField.get_moab().get_entities_by_handle(m->meshset, ents, true);
250 return ents;
251 };
252
253 const auto json_parameters =
254 mField.getInterface<JsonConfigManager>()->getParamsFromBlockset(
255 "MAT_NEOHOOKEAN", m->getMeshsetId());
256 double c10;
257 double K;
258 if (!json_parameters.empty()) {
259 if (json_parameters.size() != 2 ||
260 json_parameters.find("c10") == json_parameters.end() ||
261 json_parameters.find("k") == json_parameters.end()) {
262 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
263 "MAT_NEOHOOKEAN JSON block must have exactly two "
264 "attributes: c10, k");
265 }
266 c10 = json_parameters.at("c10");
267 K = json_parameters.at("k");
268 } else {
269 if (block_data.size() < 2) {
270 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
271 "MAT_NEOHOOKEAN block must have at least two attributes: "
272 "c10, K");
273 }
274 c10 = block_data[0];
275 K = block_data[1];
276 }
277
278 CHKERR validateMaterialParameters(c10, K, "MAT_NEOHOOKEAN block");
279
280 blockData.push_back({c10, K, get_block_ents()});
281
282 MOFEM_LOG("EP", sev) << "MatBlock Neo-Hookean c10 = "
283 << blockData.back().c10
284 << " K = " << blockData.back().K
285 << " mu = " << getShearModulus(c10)
286 << " Abaqus D1 = " << getAbaqusD1(K)
287 << " nb ents. = "
288 << blockData.back().blockEnts.size();
289 }
291 }
292
294
295 OpSpatialPhysical(const std::string &field_name,
296 boost::shared_ptr<DataAtIntegrationPts> data_ptr,
297 const double alpha_u);
298
299 MoFEMErrorCode integrate(EntData &data);
300
301 private:
302 const double alphaU;
303 };
304
305 virtual VolUserDataOperator *
307 boost::shared_ptr<DataAtIntegrationPts> data_ptr,
308 const double alpha_u) {
309 return new OpSpatialPhysical(field_name, data_ptr, alpha_u);
310 }
311
313
315 const std::string &field_name,
316 boost::shared_ptr<DataAtIntegrationPts> data_ptr,
317 boost::shared_ptr<ExternalStrainVec> external_strain_vec_ptr,
318 std::map<std::string, boost::shared_ptr<ScalingMethod>> smv);
319
320 MoFEMErrorCode integrate(EntData &data);
321
322 private:
323 boost::shared_ptr<ExternalStrainVec> externalStrainVecPtr;
324 std::map<std::string, boost::shared_ptr<ScalingMethod>> scalingMethodsMap;
325 };
326
328 const std::string &field_name,
329 boost::shared_ptr<DataAtIntegrationPts> data_ptr,
330 boost::shared_ptr<ExternalStrainVec> external_strain_vec_ptr,
331 std::map<std::string, boost::shared_ptr<ScalingMethod>> smv) {
332 return new OpSpatialPhysicalExternalStrain(field_name, data_ptr,
333 external_strain_vec_ptr, smv);
334 }
335
337 VectorPtr external_pressure_ptr,
338 boost::shared_ptr<ExternalStrainVec> external_strain_vec_ptr,
339 std::map<std::string, boost::shared_ptr<ScalingMethod>> smv) override {
340 return new OpCalculateExternalPressure(std::move(external_pressure_ptr),
341 std::move(external_strain_vec_ptr),
342 std::move(smv));
343 }
344
346 OpSpatialPhysical_du_du(std::string row_field, std::string col_field,
347 boost::shared_ptr<DataAtIntegrationPts> data_ptr,
348 const double alpha);
349 MoFEMErrorCode getOptions();
350 MoFEMErrorCode integrate(EntData &row_data, EntData &col_data);
351
352 private:
353 const double alphaU;
354 double minimEigenValue = 0;
355 };
356
358 std::string row_field, std::string col_field,
359 boost::shared_ptr<DataAtIntegrationPts> data_ptr, const double alpha) {
360 return new OpSpatialPhysical_du_du(row_field, col_field, data_ptr, alpha);
361 }
362
363 /**
364 * @brief Recover the stretch tensor from a prescribed Biot stress.
365 *
366 * After diagonalising the Biot stress, the principal Hencky stretches
367 * \f$h_a = \log(\lambda_a)\f$ are found from
368 *
369 * \f[
370 * \mathcal{R}_a(\boldsymbol{h}) =
371 * s_a e^{h_a} - \tau_a + q = 0, \qquad
372 * \tau_a = 2c_{10}J^{-2/3}
373 * \left(e^{2h_a} - \frac{I_1}{3}\right) + KJ(J-1),
374 * \quad I_1 = \sum_b e^{2h_b},
375 * \f]
376 *
377 * where \f$s_a\f$ is a principal Biot stress and
378 * \f$q=3K_{\rm ext}\varepsilon_{\rm ext}\f$ is prescribed at the
379 * integration point. The recovered stretch is
380 * \f$\mathbf{U} = \mathbf{Q}\,\mathrm{diag}(e^{h_a})\mathbf{Q}^T\f$.
381 */
382 template <typename T_Biota, typename T_Stretch>
384
385 MoFEMErrorCode evaluateRhs();
386 MoFEMErrorCode evaluateLhs();
387 MoFEMErrorCode evaluateFullLhs();
389 MoFEMErrorCode calculateStretch();
390 MoFEMErrorCode calculateBiotStretch();
392 MoFEMErrorCode logSnesFailure(const PetscErrorCode snes_solve_error,
393 const PetscErrorCode accepted_state_error,
394 const PetscErrorCode reason_query_error);
395 MoFEMErrorCode setUPSnes();
396 static MoFEMErrorCode snesObjective(SNES snes, Vec x, PetscReal *objective,
397 void *ctx);
398 static MoFEMErrorCode snesRhs(SNES snes, Vec x, Vec r, void *ctx);
399 static MoFEMErrorCode snesLhs(SNES snes, Vec x, Mat A, Mat B, void *ctx);
400
410 MatrixDouble tPrincipalDReMat;
411 MatrixDouble tDReMat;
413 /// SNES unknown: principal Hencky stretches.
415 SmartPetscObj<Mat> A;
416 SmartPetscObj<Vec> R;
417 SmartPetscObj<Vec> Chi;
418 SmartPetscObj<SNES> sNes;
419 double c10;
420 double K;
422 int max_iter = 100;
423 double tol = 1e-12;
424 double minimEigenValue = 0.;
425 bool allowNonConverged = true;
427 std::numeric_limits<double>::infinity();
429 };
430
433 boost::shared_ptr<DataAtIntegrationPts> data_ptr,
434 boost::shared_ptr<MatrixDouble> strain_ptr,
435 boost::shared_ptr<MatrixDouble> stress_ptr,
436 boost::shared_ptr<HMHNeohookean> neohookean_ptr,
437 VectorPtr external_pressure_ptr);
438 MoFEMErrorCode doWork(int side, EntityType type, EntData &data);
439
440 private:
441 boost::shared_ptr<DataAtIntegrationPts> dataAtPts;
442 boost::shared_ptr<MatrixDouble> strainPtr;
443 boost::shared_ptr<MatrixDouble> stressPtr;
444 boost::shared_ptr<HMHNeohookean> neohookeanPtr;
447 };
448
450 boost::shared_ptr<DataAtIntegrationPts> data_ptr,
451 boost::shared_ptr<PhysicalEquations> physics_ptr,
452 boost::shared_ptr<MatrixDouble> strain_ptr) override {
453 return returnOpCalculateStretchFromStress(std::move(data_ptr),
454 std::move(physics_ptr),
455 std::move(strain_ptr), nullptr);
456 }
457
459 boost::shared_ptr<DataAtIntegrationPts> data_ptr,
460 boost::shared_ptr<PhysicalEquations> physics_ptr,
461 boost::shared_ptr<MatrixDouble> strain_ptr,
462 VectorPtr external_pressure_ptr) override {
463 auto neohookean_ptr =
464 boost::dynamic_pointer_cast<HMHNeohookean>(physics_ptr);
465 if (!neohookean_ptr) {
467 "Pointer to HMHNeohookean is null");
468 }
470 data_ptr,
471 strain_ptr ? strain_ptr : data_ptr->getLogStretchTensorAtPts(),
472 data_ptr->getApproxPAtPts(), neohookean_ptr,
473 std::move(external_pressure_ptr));
474 }
475
476private:
478
480 double K_default;
482 double lambdaMinU = 1e-4;
483 double lambdaMaxU = 1e4;
484 double betaStretchBoxMin = 1e-6;
485 double betaStretchBoxMax = 1e-8;
486 struct BlockData {
487 double c10;
488 double K;
490 };
491 std::vector<BlockData> blockData;
492};
493
495 const std::string &field_name,
496 boost::shared_ptr<DataAtIntegrationPts> data_ptr, const double alpha_u)
497 : OpAssembleVolume(field_name, data_ptr, OPROW), alphaU(alpha_u) {}
498
499extern "C" {
500void tetcircumcenter_tp(double a[3], double b[3], double c[3], double d[3],
501 double circumcenter[3], double *xi, double *eta,
502 double *zeta);
503}
504
507
508 auto neohookean_ptr =
509 boost::dynamic_pointer_cast<HMHNeohookean>(dataAtPts->physicsPtr);
510 if (!neohookean_ptr) {
511 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
512 "Pointer to HMHNeohookean is null");
513 }
514 auto [def_c10, def_K] =
515 neohookean_ptr->getMaterialParameters(getFEEntityHandle());
516
517 const double c10 = def_c10;
518 const double alpha_u = alphaU;
519 const double bulk_modulus = def_K;
520 const double alpha_grad_u = neohookean_ptr->alphaGradU;
521
524
525 int nb_dofs = data.getIndices().size();
526 int nb_integration_pts = data.getN().size1();
527 auto v = getVolume();
528 auto t_w = getFTensor0IntegrationWeight();
529 auto t_approx_P_adjoint_log_du =
530 dataAtPts->getFTensorAdjointPdU(nb_integration_pts);
531 auto t_u = dataAtPts->getFTensorStretch(nb_integration_pts);
532 auto t_dot_log_u = dataAtPts->getFTensorLogStretchDot(nb_integration_pts);
533 auto t_diff_u = dataAtPts->getFTensorDiffStretch(nb_integration_pts);
534 auto t_grad_log_u =
535 dataAtPts->getFTensorGradLogStretchDot(nb_integration_pts);
536 auto t_log_u2_h1 = dataAtPts->getFTensorLogStretch2H1(nb_integration_pts);
537
538 auto t_eigen_vals = dataAtPts->getFTensorEigenVals(nb_integration_pts);
539 auto t_eigen_vecs = dataAtPts->getFTensorEigenVecs(nb_integration_pts);
540 auto &nbUniq = dataAtPts->nbUniq;
541 auto t_nb_uniq =
542 FTensor::Tensor0<FTensor::PackPtr<int *, 1>>(nbUniq.data().data());
543
544 auto t_diff = FTensor::DiffTensor<double>();
545
546 FTENSOR_INDEXES(SPACE_DIM, i, j, k, l, m, n);
547
548 auto get_ftensor2 = [](auto &v) {
550 &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]);
551 };
552
553 int nb_base_functions = data.getN().size2();
554 auto t_row_base_fun = data.getFTensor0N();
555 auto t_grad_base_fun = data.getFTensor1DiffN<3>();
556
557 auto no_h1 = [&]() {
559
560 for (int gg = 0; gg != nb_integration_pts; ++gg) {
561 double a = v * t_w;
562 ++t_w;
563
564 const auto principal_state =
565 getPrincipalState(bulk_modulus, t_eigen_vals);
566 CHKERR validatePrincipalState(principal_state, "spatial residual");
567 auto coordinate_stress = [c10, principal_state](const double v) {
568 return getPrincipalCoordinateStress(c10, principal_state, v);
569 };
570 auto t_neohookean_hencky =
571 EigenMatrix::getMat(t_eigen_vals, t_eigen_vecs, coordinate_stress);
572
574 t_P(L) = t_L(i, j, L) * t_neohookean_hencky(i, j);
575
576 ++t_eigen_vals;
577 ++t_eigen_vecs;
578 ++t_nb_uniq;
579
581 t_viscous_P(L) = alpha_u * (t_L(i, j, L) * t_dot_log_u(i, j));
582
584 t_residual(L) = t_approx_P_adjoint_log_du(L) - t_P(L) - t_viscous_P(L);
585 t_residual(L) *= a;
586
588 t_grad_residual(L, i) = alpha_grad_u * t_grad_log_u(L, i);
589 t_grad_residual(L, i) *= a;
590
591 ++t_approx_P_adjoint_log_du;
592 ++t_dot_log_u;
593 ++t_grad_log_u;
594
595 auto t_nf = getFTensor1FromPtr<size_symm>(&*nF.data().begin());
596 int bb = 0;
597 for (; bb != nb_dofs / size_symm; ++bb) {
598 t_nf(L) -= t_row_base_fun * t_residual(L);
599 t_nf(L) += t_grad_base_fun(i) * t_grad_residual(L, i);
600 ++t_nf;
601 ++t_row_base_fun;
602 ++t_grad_base_fun;
603 }
604 for (; bb != nb_base_functions; ++bb) {
605 ++t_row_base_fun;
606 ++t_grad_base_fun;
607 }
608 }
609
611 };
612
613 auto large = [&]() {
615 SETERRQ(PETSC_COMM_SELF, MOFEM_NOT_IMPLEMENTED,
616 "Not implemented for Neo-Hookean (used ADOL-C)");
618 };
619
622 CHKERR no_h1();
623 break;
624 case LARGE_ROT:
625 case MODERATE_ROT:
626 CHKERR large();
627 break;
628 default:
629 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
630 "gradApproximator not handled");
631 };
632
634}
635
637 const std::string &field_name,
638 boost::shared_ptr<DataAtIntegrationPts> data_ptr,
639 boost::shared_ptr<ExternalStrainVec> external_strain_vec_ptr,
640 std::map<std::string, boost::shared_ptr<ScalingMethod>> smv)
641 : OpAssembleVolume(field_name, data_ptr, OPROW),
642 externalStrainVecPtr(external_strain_vec_ptr), scalingMethodsMap{smv} {}
643
644MoFEMErrorCode
647
648 auto neohookean_ptr =
649 boost::dynamic_pointer_cast<HMHNeohookean>(dataAtPts->physicsPtr);
650 if (!neohookean_ptr) {
651 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
652 "Pointer to HMHNeohookean is null");
653 }
654
655 double time = OpAssembleVolume::getFEMethod()->ts_t;
658 }
659 // get entity of tet
660 EntityHandle fe_ent = OpAssembleVolume::getFEEntityHandle();
661 // iterate over all block data
662
663 for (auto &ext_strain_block : (*externalStrainVecPtr)) {
664 auto block_name = "(.*)ANALYTICAL_EXTERNALSTRAIN(.*)";
665 std::regex reg_name(block_name);
666 if (std::regex_match(ext_strain_block.blockName, reg_name)) {
667 SETERRQ(PETSC_COMM_SELF, MOFEM_NOT_IMPLEMENTED,
668 "Analytical external strain not implemented for Neo-Hookean "
669 "material.");
670 }
671 // check if finite element entity is part of the EXTERNALSTRAIN block
672 if (ext_strain_block.ents.find(fe_ent) != ext_strain_block.ents.end()) {
673 double scale = 1;
674 if (scalingMethodsMap.find(ext_strain_block.blockName) !=
675 scalingMethodsMap.end()) {
676 scale *=
677 scalingMethodsMap.at(ext_strain_block.blockName)->getScale(time);
678 } else {
679 MOFEM_LOG("SELF", Sev::warning)
680 << "No scaling method found for " << ext_strain_block.blockName;
681 }
682
683 // get ExternalStrain block data
684 double external_strain_val = scale * ext_strain_block.val;
685 double K = ext_strain_block.bulkModulusK;
686
689 constexpr auto t_kd = FTensor::Kronecker_Delta<int>();
690
691 int nb_dofs = data.getIndices().size();
692 int nb_integration_pts = data.getN().size1();
693 auto vol = getVolume();
694 auto t_w = getFTensor0IntegrationWeight();
695
696 FTENSOR_INDEXES(SPACE_DIM, i, j, k, l, m, n);
697
698 int nb_base_functions = data.getN().size2();
699 auto t_row_base_fun = data.getFTensor0N();
700
701 const double bulk_modulus = K;
702 const double diag_val = external_strain_val;
703 // Preserve the block's original meaning as an independent linear
704 // hydrostatic eigenstress.
705 const double sigma_J = bulk_modulus * 3. * diag_val;
706
707 for (int gg = 0; gg != nb_integration_pts; ++gg) {
708 double a = vol * t_w;
709 ++t_w;
710
712 t_residual(L) = (t_L(i, j, L) * t_kd(i, j)) * sigma_J;
713 t_residual(L) *= a;
714
715 auto t_nf = getFTensor1FromPtr<size_symm>(&*nF.data().begin());
716 int bb = 0;
717 for (; bb != nb_dofs / size_symm; ++bb) {
718 t_nf(L) -= t_row_base_fun * t_residual(L);
719 ++t_nf;
720 ++t_row_base_fun;
721 }
722 for (; bb != nb_base_functions; ++bb) {
723 ++t_row_base_fun;
724 }
725 }
726 }
727 }
729}
730
732 std::string row_field, std::string col_field,
733 boost::shared_ptr<DataAtIntegrationPts> data_ptr, const double alpha)
734 : OpAssembleVolumePositiveDefine(row_field, col_field, data_ptr, OPROWCOL,
735 false),
736 alphaU(alpha) {
737
738 CHK_THROW_MESSAGE(getOptions(), "get options failed");
739
740 sYmm = false;
741}
742
745 PetscOptionsBegin(PETSC_COMM_WORLD, "neo_hookean_", "", "none");
746 CHKERR PetscOptionsScalar("-min_eigen_value", "Minimum eigenvalue", "",
747 minimEigenValue, &minimEigenValue, PETSC_NULLPTR);
748 PetscOptionsEnd();
749 MOFEM_LOG("EP", Sev::inform)
750 << "Neo-Hookean min_eigen_value = " << minimEigenValue;
752}
753
754MoFEMErrorCode
756 EntData &col_data) {
758
759 auto neohookean_ptr =
760 boost::dynamic_pointer_cast<HMHNeohookean>(dataAtPts->physicsPtr);
761 if (!neohookean_ptr) {
762 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
763 "Pointer to HMHNeohookean is null");
764 }
765 auto [def_c10, def_K] =
766 neohookean_ptr->getMaterialParameters(getFEEntityHandle());
767
768 const double c10 = def_c10;
769 const double alpha_u = alphaU;
770 const double bulk_modulus = def_K;
771 const double alpha_grad_u = neohookean_ptr->alphaGradU;
772
774
775 constexpr auto t_kd_sym = FTensor::Kronecker_Delta_symmetric<int>();
776
778 auto t_diff = FTensor::DiffTensor<double>();
779
780 int nb_integration_pts = row_data.getN().size1();
781 int row_nb_dofs = row_data.getIndices().size();
782 int col_nb_dofs = col_data.getIndices().size();
783
784 auto get_ftensor2 = [](MatrixDouble &m, const int r, const int c) {
786 size_symm>(
787
788 &m(r + 0, c + 0), &m(r + 0, c + 1), &m(r + 0, c + 2), &m(r + 0, c + 3),
789 &m(r + 0, c + 4), &m(r + 0, c + 5),
790
791 &m(r + 1, c + 0), &m(r + 1, c + 1), &m(r + 1, c + 2), &m(r + 1, c + 3),
792 &m(r + 1, c + 4), &m(r + 1, c + 5),
793
794 &m(r + 2, c + 0), &m(r + 2, c + 1), &m(r + 2, c + 2), &m(r + 2, c + 3),
795 &m(r + 2, c + 4), &m(r + 2, c + 5),
796
797 &m(r + 3, c + 0), &m(r + 3, c + 1), &m(r + 3, c + 2), &m(r + 3, c + 3),
798 &m(r + 3, c + 4), &m(r + 3, c + 5),
799
800 &m(r + 4, c + 0), &m(r + 4, c + 1), &m(r + 4, c + 2), &m(r + 4, c + 3),
801 &m(r + 4, c + 4), &m(r + 4, c + 5),
802
803 &m(r + 5, c + 0), &m(r + 5, c + 1), &m(r + 5, c + 2), &m(r + 5, c + 3),
804 &m(r + 5, c + 4), &m(r + 5, c + 5)
805
806 );
807 };
808
809 FTENSOR_INDEXES(SPACE_DIM, i, j, k, l, m, n);
810
811 auto v = getVolume();
812 auto ts_a = getTSa();
813 auto t_w = getFTensor0IntegrationWeight();
814
815 int row_nb_base_functions = row_data.getN().size2();
816 auto t_row_base_fun = row_data.getFTensor0N();
817 auto t_row_grad_fun = row_data.getFTensor1DiffN<3>();
818
819 auto t_diff_u = dataAtPts->getFTensorDiffStretch(nb_integration_pts);
820 auto t_log_u2_h1 = dataAtPts->getFTensorLogStretch2H1(nb_integration_pts);
821 auto t_u = dataAtPts->getFTensorStretch(nb_integration_pts);
822 auto t_approx_P_adjoint_dstretch =
823 dataAtPts->getFTensorAdjointPdstretch(nb_integration_pts);
824 auto t_eigen_vals = dataAtPts->getFTensorEigenVals(nb_integration_pts);
825 auto t_eigen_vecs = dataAtPts->getFTensorEigenVecs(nb_integration_pts);
826 auto &nbUniq = dataAtPts->nbUniq;
827 auto t_nb_uniq =
828 FTensor::Tensor0<FTensor::PackPtr<int *, 1>>(nbUniq.data().data());
829
830 auto no_h1 = [&]() {
832
833 for (int gg = 0; gg != nb_integration_pts; ++gg) {
834 double a = v * t_w;
835 ++t_w;
836
837 const auto principal_state =
838 getPrincipalState(bulk_modulus, t_eigen_vals);
839 CHKERR validatePrincipalState(principal_state, "spatial tangent");
840 auto coordinate_stress = [c10, principal_state](const double v) {
841 return getPrincipalCoordinateStress(c10, principal_state, v);
842 };
843 auto coordinate_stress_derivative = [c10,
844 principal_state](const double v) {
846 c10, principal_state, v);
847 };
848 auto squared_stretch = [](const double v) { return std::exp(2. * v); };
849 auto identity = [](const double) { return 1.; };
850 auto isochoric_coordinate_stress = [c10,
851 principal_state](const double v) {
852 return getShearModulus(c10) * principal_state.jacobianToMinusTwoThirds *
853 (std::exp(2. * v) - principal_state.firstInvariant / 3.);
854 };
855 auto t_diff_neohookean =
856 EigenMatrix::getDiffMat(t_eigen_vals, t_eigen_vecs, coordinate_stress,
857 coordinate_stress_derivative, t_nb_uniq);
858 auto t_squared_stretch =
859 EigenMatrix::getMat(t_eigen_vals, t_eigen_vecs, squared_stretch);
860 auto t_identity =
861 EigenMatrix::getMat(t_eigen_vals, t_eigen_vecs, identity);
862 auto t_isochoric_coordinate_stress = EigenMatrix::getMat(
863 t_eigen_vals, t_eigen_vecs, isochoric_coordinate_stress);
864 const double scaled_shear_modulus =
865 getShearModulus(c10) * principal_state.jacobianToMinusTwoThirds;
867 t_material_tangent(i, j, k, l) =
868 t_diff_neohookean(i, j, k, l) -
869 (2. / 3.) * t_isochoric_coordinate_stress(i, j) * t_identity(k, l) -
870 (2. / 3.) * scaled_shear_modulus * t_identity(i, j) *
871 t_squared_stretch(k, l) +
872 principal_state.volumetricTangent * t_identity(i, j) *
873 t_identity(k, l);
874
876 t_dP(L, J) =
877 t_L(i, j, L) * (t_material_tangent(i, j, k, l) * t_L(k, l, J));
878 t_dP(L, J) += (alpha_u * ts_a) *
879 (t_L(i, j, L) * (t_diff(i, j, k, l) * t_L(k, l, J)));
880
882 t_deltaP(i, j) = (t_approx_P_adjoint_dstretch(i, j) ||
883 t_approx_P_adjoint_dstretch(j, i)) /
884 2.;
885 auto t_diff2_uP = EigenMatrix::getDiffDiffMat(
886 t_eigen_vals, t_eigen_vecs, static_cast<double (*)(double)>(std::exp),
887 static_cast<double (*)(double)>(std::exp),
888 static_cast<double (*)(double)>(std::exp), t_deltaP, t_nb_uniq);
889 t_dP(L, J) -= t_L(i, j, L) * (t_diff2_uP(i, j, k, l) * t_L(k, l, J));
890 ++t_approx_P_adjoint_dstretch;
891 ++t_eigen_vals;
892 ++t_eigen_vecs;
893 ++t_nb_uniq;
894
895 t_dP(L, J) *= a;
896 if (minimEigenValue > 0) {
897 // Symmetrize tangesnt stiffness matrix, and add calulate eigen values,
898 // tham add minimum eigen value to the tangent stiffness matrix
900 t_hessian_eig_vecs(L, J) = 0.5 * (t_dP(L, J) + t_dP(J, L));
901 FTensor::Tensor1<double, size_symm> t_hessian_eig_vals;
902 CHKERR computeEigenValuesSymmetric(t_hessian_eig_vecs,
903 t_hessian_eig_vals);
904 const double eigenvalue_floor = bulk_modulus * minimEigenValue;
905 bool project_hessian = false;
906 for (int aa = 0; aa != size_symm; ++aa) {
907 project_hessian =
908 project_hessian || t_hessian_eig_vals(aa) < eigenvalue_floor;
909 }
910 if (project_hessian) {
911 auto min_eig_val = [eigenvalue_floor](double v) {
912 return (v + eigenvalue_floor + std::abs(v - eigenvalue_floor)) / 2.;
913 };
914 auto t_dP_min_eig = EigenMatrix::getMat(
915 t_hessian_eig_vals, t_hessian_eig_vecs, min_eig_val);
916 t_dP(L, J) = t_dP_min_eig(L, J);
917 }
918 }
919
920 int rr = 0;
921 for (; rr != row_nb_dofs / size_symm; ++rr) {
922 auto t_col_base_fun = col_data.getFTensor0N(gg, 0);
923 auto t_col_grad_fun = col_data.getFTensor1DiffN<3>(gg, 0);
924
925 auto t_m = get_ftensor2(K, 6 * rr, 0);
926 for (int cc = 0; cc != col_nb_dofs / size_symm; ++cc) {
927 double b = t_row_base_fun * t_col_base_fun;
928 t_m(L, J) += b * t_dP(L, J);
929 double c = (a * alpha_grad_u * ts_a) *
930 (t_row_grad_fun(i) * t_col_grad_fun(i));
931 t_m(L, J) += c * t_kd_sym(L, J);
932
933 ++t_m;
934 ++t_col_base_fun;
935 ++t_col_grad_fun;
936 }
937 ++t_row_base_fun;
938 ++t_row_grad_fun;
939 }
940
941 for (; rr != row_nb_base_functions; ++rr) {
942 ++t_row_base_fun;
943 ++t_row_grad_fun;
944 }
945 }
947 };
948
949 auto large = [&]() {
951 SETERRQ(PETSC_COMM_SELF, MOFEM_NOT_IMPLEMENTED,
952 "Not implemented for Neo-Hookean (used ADOL-C)");
954 };
955
958 CHKERR no_h1();
959 break;
960 case LARGE_ROT:
961 case MODERATE_ROT:
962 CHKERR large();
963 break;
964 default:
965 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
966 "gradApproximator not handled");
967 };
968
970}
971
973 boost::shared_ptr<DataAtIntegrationPts> data_ptr,
974 boost::shared_ptr<MatrixDouble> strain_ptr,
975 boost::shared_ptr<MatrixDouble> stress_ptr,
976 boost::shared_ptr<HMHNeohookean> neohookean_ptr,
977 VectorPtr external_pressure_ptr)
978 : VolUserDataOperator(H1, OPLAST), dataAtPts(data_ptr),
979 strainPtr(strain_ptr), stressPtr(stress_ptr),
980 neohookeanPtr(neohookean_ptr),
981 externalPressurePtr(std::move(external_pressure_ptr)) {
982 std::fill(&doEntities[MBVERTEX], &doEntities[MBMAXTYPE], false);
983 doEntities[MBVERTEX] = true;
985 "setUPSnes failed for Neo-Hookean stretch solve");
986}
987
988MoFEMErrorCode
990 EntData &data) {
992
993 FTENSOR_INDEXES(SPACE_DIM, i, j, k, l, m, n);
995
996 const int nb_integration_pts = stressPtr->size1();
997#ifndef NDEBUG
998 if (nb_integration_pts != getGaussPts().size2()) {
999 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
1000 "inconsistent number of integration points");
1001 }
1002#endif // NDEBUG
1003 if (externalPressurePtr &&
1004 externalPressurePtr->size() != nb_integration_pts) {
1005 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
1006 "Inconsistent number of external-pressure integration points");
1007 }
1008
1009 MatrixSizeHelper<GetFTensor2SymmetricFromMatType<SPACE_DIM, -1, DL>,
1010 DL>::size(*strainPtr, nb_integration_pts);
1011 MatrixSizeHelper<GetFTensor2SymmetricFromMatType<SPACE_DIM, -1, DL>,
1012 DL>::size(*dataAtPts->getStretchTensorAtPts(),
1013 nb_integration_pts);
1014 MatrixSizeHelper<GetFTensor4DdgFromMatType<SPACE_DIM, SPACE_DIM, -1, DL>,
1015 DL>::size(*dataAtPts->getDiffStretchTensorAtPts(),
1016 nb_integration_pts);
1017 MatrixSizeHelper<GetFTensor1FromMatType<SPACE_DIM, -1, DL>, DL>::size(
1018 *dataAtPts->getEigenVals(), nb_integration_pts);
1019 MatrixSizeHelper<GetFTensor2FromMatType<SPACE_DIM, SPACE_DIM, -1, DL>,
1020 DL>::size(*dataAtPts->getEigenVecs(), nb_integration_pts);
1021 dataAtPts->nbUniq.resize(nb_integration_pts, false);
1022 MatrixSizeHelper<GetFTensor4DdgFromMatType<SPACE_DIM, SPACE_DIM, -1, DL>,
1023 DL>::size(dataAtPts->matD, nb_integration_pts);
1024 MatrixSizeHelper<GetFTensor4DdgFromMatType<SPACE_DIM, SPACE_DIM, -1, DL>,
1025 DL>::size(dataAtPts->matInvD, nb_integration_pts);
1026 MatrixSizeHelper<GetFTensor2SymmetricFromMatType<SPACE_DIM, -1, DL>,
1027 DL>::size(dataAtPts->logStretch2H1AtPts, nb_integration_pts);
1028 MatrixSizeHelper<GetFTensor2SymmetricFromMatType<SPACE_DIM, -1, DL>,
1029 DL>::size(dataAtPts->logStretchTotalTensorAtPts,
1030 nb_integration_pts);
1031 MatrixSizeHelper<GetFTensor2FromMatType<SPACE_DIM, SPACE_DIM, -1, DL>,
1032 DL>::size(dataAtPts->rotMatAtPts, nb_integration_pts);
1033 MatrixSizeHelper<GetFTensor2FromMatType<SPACE_DIM, SPACE_DIM, -1, DL>,
1034 DL>::size(*dataAtPts->getAdjointPdstretchAtPts(),
1035 nb_integration_pts);
1036
1037 strainPtr->clear();
1038 dataAtPts->getStretchTensorAtPts()->clear();
1039 dataAtPts->getDiffStretchTensorAtPts()->clear();
1040 dataAtPts->getEigenVals()->clear();
1041 dataAtPts->getEigenVecs()->clear();
1042 dataAtPts->nbUniq.clear();
1043 dataAtPts->matD.clear();
1044 dataAtPts->matInvD.clear();
1045 dataAtPts->logStretch2H1AtPts.clear();
1046 dataAtPts->logStretchTotalTensorAtPts.clear();
1047
1048 auto t_strain = getFTensor2SymmetricFromMat<SPACE_DIM, -1, DL>(*strainPtr);
1049 auto t_biot_stretch = dataAtPts->getFTensorStretch(nb_integration_pts);
1050 auto t_diff_stretch = dataAtPts->getFTensorDiffStretch(nb_integration_pts);
1051 auto t_stress = getFTensor2FromMat<SPACE_DIM, SPACE_DIM, -1, DL>(*stressPtr);
1052 auto t_omega = dataAtPts->getFTensorRotAxis(nb_integration_pts);
1053 auto t_R = dataAtPts->getFTensorRotMat(nb_integration_pts);
1054 auto t_biot_stress =
1055 dataAtPts->getFTensorAdjointPdstretch(nb_integration_pts);
1056 auto t_eigen_vals = dataAtPts->getFTensorEigenVals(nb_integration_pts);
1057 auto t_eigen_vecs = dataAtPts->getFTensorEigenVecs(nb_integration_pts);
1058 auto t_mat_d =
1059 getFTensor4DdgFromMat<SPACE_DIM, SPACE_DIM, -1, DL>(dataAtPts->matD);
1060 auto t_mat_inv_d =
1061 getFTensor4DdgFromMat<SPACE_DIM, SPACE_DIM, -1, DL>(dataAtPts->matInvD);
1062 auto t_log_u2_h1 = dataAtPts->getFTensorLogStretch2H1(nb_integration_pts);
1063 auto t_log_stretch_total =
1064 dataAtPts->getFTensorLogStretchTotal(nb_integration_pts);
1066 dataAtPts->nbUniq.data().data());
1067
1068 const auto [def_c10, def_K] =
1069 neohookeanPtr->getMaterialParameters(getFEEntityHandle());
1070 const double c10 = def_c10;
1071 const double bulk_modulus = def_K;
1072 CHKERR validateMaterialParameters(c10, bulk_modulus,
1073 "no-stretch integration point");
1074
1076 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
1077 "Rotation selector not handled by Abaqus Neo-Hookean");
1078 }
1079
1080 auto &stretch_from_stress = stretchFromStress;
1081 stretch_from_stress.c10 = c10;
1082 stretch_from_stress.K = bulk_modulus;
1083 stretch_from_stress.allowNonConverged = true;
1084 constexpr auto t_diff_sym = FTensor::DiffSymmetrize<double>();
1085
1086 for (int gg = 0; gg != nb_integration_pts; ++gg) {
1087 t_R(i, j) = LieGroups::SO3::exp(t_omega, t_omega.l2())(i, j);
1088
1090 t_rotated_stress(i, j) = t_R(k, i) * t_stress(k, j);
1091 t_biot_stress(i, j) = t_diff_sym(i, j, k, l) * t_rotated_stress(k, l);
1092 stretch_from_stress.tBiotStress(i, j) =
1093 (t_biot_stress(i, j) || t_biot_stress(j, i)) / 2.;
1094 stretch_from_stress.tHenckyStreach(i, j) = t_strain(i, j);
1095 stretch_from_stress.externalPressure =
1096 externalPressurePtr ? (*externalPressurePtr)[gg] : 0.;
1097
1098 CHKERR stretch_from_stress.calculateStretch();
1099 CHKERR stretch_from_stress.calculateDHenckyStreachDBiotStress();
1100
1101 t_strain(i, j) = stretch_from_stress.tHenckyStreach(i, j);
1102 t_log_stretch_total(i, j) = stretch_from_stress.tHenckyStreach(i, j);
1103 t_log_u2_h1(i, j) = 0;
1104 t_biot_stretch(i, j) = stretch_from_stress.tBiotStreach(i, j);
1105
1106 t_eigen_vals(i) = stretch_from_stress.tHenckyStreachEigenVals(i);
1107 t_eigen_vecs(i, j) = stretch_from_stress.tBiotStressEigenVecs(i, j);
1108 t_nb_uniq =
1109 getUniqNb<SPACE_DIM>(getVectorAdaptor(&t_eigen_vals(0), SPACE_DIM),
1111 if (t_nb_uniq < SPACE_DIM) {
1112 CHKERR sortEigenVals<SPACE_DIM>(
1113 getVectorAdaptor(&t_eigen_vals(0), SPACE_DIM),
1114 getMatrixAdaptor(&t_eigen_vecs(0, 0), SPACE_DIM, SPACE_DIM),
1116 }
1117
1118 auto t_diff_stretch_mat = EigenMatrix::getDiffMat(
1119 t_eigen_vals, t_eigen_vecs, static_cast<double (*)(double)>(std::exp),
1120 static_cast<double (*)(double)>(std::exp), t_nb_uniq);
1121 t_diff_stretch(i, j, k, l) = t_diff_stretch_mat(i, j, k, l);
1122
1123 MatrixDouble d_biot_stretch_d_biot_stress_mat;
1124 auto get_d_biot_stretch_d_biot_stress = MatrixSizeHelper<
1125 GetFTensor4DdgFromMatType<SPACE_DIM, SPACE_DIM, -1, DL>,
1126 DL>::size(d_biot_stretch_d_biot_stress_mat, 1);
1127 auto t_d_biot_stretch_d_biot_stress = get_d_biot_stretch_d_biot_stress();
1128
1129 t_d_biot_stretch_d_biot_stress(i, j, k, l) =
1130 t_diff_stretch(i, j, m, n) *
1131 stretch_from_stress.tDHenckyStreachDBiotStress(m, n, k, l);
1132
1133 // The tensor chain above is the major-symmetric compliance C = dU/dS in
1134 // DDg component storage. Positive definiteness belongs to C, not to its
1135 // generally nonsymmetric packed action matrix Q = C W.
1136 d_biot_stretch_d_biot_stress_mat.resize(size_symm, size_symm, false);
1137 auto t_packed_compliance = getFTensor2FromMat<size_symm, size_symm>(
1138 d_biot_stretch_d_biot_stress_mat);
1140 t_compliance_eig_vecs(L, J) =
1141 0.5 * (t_packed_compliance(L, J) + t_packed_compliance(J, L));
1142 FTensor::Tensor1<double, size_symm> t_compliance_eig_vals;
1143 CHKERR computeEigenValuesSymmetric(t_compliance_eig_vecs,
1144 t_compliance_eig_vals);
1145 const double numerical_relative_floor =
1146 std::sqrt(std::numeric_limits<double>::epsilon());
1147 double compliance_spectral_radius = 0.;
1148 for (int aa = 0; aa != size_symm; ++aa) {
1149 compliance_spectral_radius = std::max(
1150 compliance_spectral_radius, std::abs(t_compliance_eig_vals(aa)));
1151 }
1152 // The material floor alone does not control the condition number. If one
1153 // compliance eigenvalue becomes very large, reconstructing V diag(lambda)
1154 // V^T can round a fixed small eigenvalue to zero. Tie the numerical floor
1155 // to the spectral radius so every retained mode remains representable.
1156 const double material_compliance_eigenvalue_floor =
1157 stretch_from_stress.minimEigenValue /
1158 std::max(std::abs(stretch_from_stress.K),
1159 std::numeric_limits<double>::min());
1160 const double numerical_compliance_eigenvalue_floor =
1161 numerical_relative_floor * compliance_spectral_radius;
1162 const double compliance_eigenvalue_floor =
1163 std::max(material_compliance_eigenvalue_floor,
1164 numerical_compliance_eigenvalue_floor);
1165 bool project_compliance = false;
1166 for (int aa = 0; aa != size_symm; ++aa) {
1167 project_compliance =
1168 project_compliance ||
1169 t_compliance_eig_vals(aa) < compliance_eigenvalue_floor;
1170 }
1171 if (project_compliance) {
1172 auto floor_compliance_eigenvalue =
1173 [compliance_eigenvalue_floor](const double v) {
1174 return (v + compliance_eigenvalue_floor +
1175 std::abs(v - compliance_eigenvalue_floor)) /
1176 2.;
1177 };
1178 auto t_projected_compliance =
1179 EigenMatrix::getMat(t_compliance_eig_vals, t_compliance_eig_vecs,
1180 floor_compliance_eigenvalue);
1181 t_packed_compliance(L, J) = t_projected_compliance(L, J);
1182 }
1183
1184 // Store the projected compliance directly in DDg component storage.
1185 auto d_biot_stress_d_hencky_mat = d_biot_stretch_d_biot_stress_mat;
1186 d_biot_stretch_d_biot_stress_mat.resize(1, size_symm * size_symm, false);
1187 t_mat_inv_d(i, j, k, l) = t_d_biot_stretch_d_biot_stress(i, j, k, l);
1188
1189 // Tensor contraction acts through Q = C W. Invert Q, then convert Q^-1
1190 // back to DDg component storage Q^-1 W^-1.
1191 for (int row = 0; row != size_symm; ++row) {
1192 for (const int col : {1, 2, 4}) {
1193 d_biot_stress_d_hencky_mat(row, col) *= 2.;
1194 }
1195 }
1196 CHKERR computeMatrixInverse(d_biot_stress_d_hencky_mat);
1197 for (int row = 0; row != size_symm; ++row) {
1198 for (const int col : {1, 2, 4}) {
1199 d_biot_stress_d_hencky_mat(row, col) *= 0.5;
1200 }
1201 }
1202 auto get_d_biot_stress_d_hencky = MatrixSizeHelper<
1203 GetFTensor4DdgFromMatType<SPACE_DIM, SPACE_DIM, -1, DL>,
1204 DL>::size(d_biot_stress_d_hencky_mat, 1);
1205 auto t_d_biot_stress_d_hencky = get_d_biot_stress_d_hencky();
1206 t_mat_d(i, j, k, l) = t_d_biot_stress_d_hencky(i, j, k, l);
1207
1208 ++t_strain;
1209 ++t_biot_stretch;
1210 ++t_diff_stretch;
1211 ++t_stress;
1212 ++t_omega;
1213 ++t_R;
1214 ++t_biot_stress;
1215 ++t_eigen_vals;
1216 ++t_eigen_vecs;
1217 ++t_mat_d;
1218 ++t_mat_inv_d;
1219 ++t_log_u2_h1;
1220 ++t_log_stretch_total;
1221 ++t_nb_uniq;
1222 }
1223
1225}
1226
1227template <typename T_Biota, typename T_Stretch>
1229 T_Biota, T_Stretch>::evaluateComplementaryPotential() const {
1230 double complementary_potential = getStrainEnergy(c10, K, tStretchVec);
1231 for (int aa = 0; aa != SPACE_DIM; ++aa) {
1232 const double h = tStretchVec(aa);
1233 complementary_potential -= tBiotStressEigenVals(aa) * (std::exp(h) - 1.);
1234 complementary_potential -= externalPressure * h;
1235 }
1236 return complementary_potential;
1237}
1238
1239template <typename T_Biota, typename T_Stretch>
1240MoFEMErrorCode
1243
1244 const auto principal_state = getPrincipalState(K, tStretchVec);
1245 CHKERR validatePrincipalState(principal_state, "principal residual");
1246
1247 for (int aa = 0; aa != SPACE_DIM; ++aa) {
1248 const double h = tStretchVec(aa);
1249 tReVec(aa) = std::exp(h) * tBiotStressEigenVals(aa) -
1250 getPrincipalCoordinateStress(c10, principal_state, h) +
1251 externalPressure;
1252 }
1253
1255}
1256
1257template <typename T_Biota, typename T_Stretch>
1258MoFEMErrorCode
1261
1262 const auto principal_state = getPrincipalState(K, tStretchVec);
1263 CHKERR validatePrincipalState(principal_state, "principal Hessian");
1264 const double scaled_shear_modulus =
1265 getShearModulus(c10) * principal_state.jacobianToMinusTwoThirds;
1266
1268 auto t_principal_d_re =
1269 getFTensor2FromMat<SPACE_DIM, SPACE_DIM>(tPrincipalDReMat);
1270 for (int aa = 0; aa != SPACE_DIM; ++aa) {
1271 const double h_a = tStretchVec(aa);
1272 const double stretch_a = std::exp(h_a);
1273 const double squared_stretch_a = stretch_a * stretch_a;
1274 const double isochoric_deviator_a =
1275 squared_stretch_a - principal_state.firstInvariant / 3.;
1276 for (int bb = 0; bb != SPACE_DIM; ++bb) {
1277 const double squared_stretch_b = std::exp(2. * tStretchVec(bb));
1278 const double isochoric_tangent =
1279 scaled_shear_modulus *
1280 ((aa == bb ? 2. * squared_stretch_a : 0.) -
1281 (2. / 3.) * squared_stretch_b - (2. / 3.) * isochoric_deviator_a);
1282 const double coordinate_stress_tangent =
1283 isochoric_tangent + principal_state.volumetricTangent;
1284 tPrincipalDReMat(aa, bb) = -coordinate_stress_tangent;
1285 if (aa == bb) {
1286 tPrincipalDReMat(aa, bb) += stretch_a * tBiotStressEigenVals(aa);
1287 }
1288 }
1289 }
1290
1291 // R = d(S:U - W)/dh, so the complementary Hessian is -dR/dh. Inspect the
1292 // unprojected Hessian even when optional Newton regularisation is enabled.
1294 t_hessian_eig_vecs(i, j) =
1295 -0.5 * (t_principal_d_re(i, j) + t_principal_d_re(j, i));
1296 FTensor::Tensor1<double, SPACE_DIM> t_hessian_eig_vals;
1297 CHKERR computeEigenValuesSymmetric(t_hessian_eig_vecs, t_hessian_eig_vals);
1298 minimumComplementaryHessianEigenvalue =
1299 std::numeric_limits<double>::infinity();
1300 complementaryHessianSpectralRadius = 0.;
1301 for (int aa = 0; aa != SPACE_DIM; ++aa) {
1302 minimumComplementaryHessianEigenvalue =
1303 std::min(minimumComplementaryHessianEigenvalue, t_hessian_eig_vals(aa));
1304 complementaryHessianSpectralRadius = std::max(
1305 complementaryHessianSpectralRadius, std::abs(t_hessian_eig_vals(aa)));
1306 }
1307
1308 if (minimEigenValue > 0) {
1309 // Project the Newton matrix to positive definite and restore the residual
1310 // Jacobian sign. The residual and the stability measurement above remain
1311 // unmodified.
1312 const double eigenvalue_floor = K * minimEigenValue;
1313 bool project_hessian = false;
1314 for (int aa = 0; aa != SPACE_DIM; ++aa) {
1315 project_hessian =
1316 project_hessian || t_hessian_eig_vals(aa) < eigenvalue_floor;
1317 }
1318 if (project_hessian) {
1319 auto floor_eigenvalue = [eigenvalue_floor](const double v) {
1320 return (v + eigenvalue_floor + std::abs(v - eigenvalue_floor)) / 2.;
1321 };
1322 auto t_projected_hessian = EigenMatrix::getMat(
1323 t_hessian_eig_vals, t_hessian_eig_vecs, floor_eigenvalue);
1324 t_principal_d_re(i, j) = -t_projected_hessian(i, j);
1325 }
1326 }
1327
1329}
1330
1331template <typename T_Biota, typename T_Stretch>
1332MoFEMErrorCode
1334 T_Stretch>::evaluateFullLhs() {
1336
1339
1341
1344 t_eigen_vecs(i, j) = tHenckyStreach(i, j);
1345
1346 CHKERR computeEigenValuesSymmetric(t_eigen_vecs, t_eigen_vals);
1347
1348 const int nb_uniq = getUniqNb<SPACE_DIM>(t_eigen_vals);
1349 if (nb_uniq < SPACE_DIM) {
1350 CHKERR sortEigenVals<SPACE_DIM>(t_eigen_vals, t_eigen_vecs);
1351 }
1352
1353 const auto principal_state = getPrincipalState(K, t_eigen_vals);
1354 CHKERR validatePrincipalState(principal_state, "full Hessian");
1355 auto coordinate_stress = [this, principal_state](const double v) {
1356 return getPrincipalCoordinateStress(c10, principal_state, v);
1357 };
1358 auto coordinate_stress_derivative = [this, principal_state](const double v) {
1360 c10, principal_state, v);
1361 };
1362 auto squared_stretch = [](const double v) { return std::exp(2. * v); };
1363 auto identity = [](const double) { return 1.; };
1364 auto isochoric_coordinate_stress = [this, principal_state](const double v) {
1365 return getShearModulus(c10) * principal_state.jacobianToMinusTwoThirds *
1366 (std::exp(2. * v) - principal_state.firstInvariant / 3.);
1367 };
1368
1369 auto t_diff_neohookean =
1370 EigenMatrix::getDiffMat(t_eigen_vals, t_eigen_vecs, coordinate_stress,
1371 coordinate_stress_derivative, nb_uniq);
1372 auto t_squared_stretch =
1373 EigenMatrix::getMat(t_eigen_vals, t_eigen_vecs, squared_stretch);
1374 auto t_identity = EigenMatrix::getMat(t_eigen_vals, t_eigen_vecs, identity);
1375 auto t_isochoric_coordinate_stress = EigenMatrix::getMat(
1376 t_eigen_vals, t_eigen_vecs, isochoric_coordinate_stress);
1377 const double scaled_shear_modulus =
1378 getShearModulus(c10) * principal_state.jacobianToMinusTwoThirds;
1379
1380 FTensor::Ddg<double, 3, 3> t_model_log_stress_diff;
1381 t_model_log_stress_diff(i, j, k, l) =
1382 t_diff_neohookean(i, j, k, l) -
1383 (2. / 3.) * t_isochoric_coordinate_stress(i, j) * t_identity(k, l) -
1384 (2. / 3.) * scaled_shear_modulus * t_identity(i, j) *
1385 t_squared_stretch(k, l) +
1386 principal_state.volumetricTangent * t_identity(i, j) * t_identity(k, l);
1387
1388 auto t_diff2_stretch_stress = EigenMatrix::getDiffDiffMat(
1389 t_eigen_vals, t_eigen_vecs, static_cast<double (*)(double)>(std::exp),
1390 static_cast<double (*)(double)>(std::exp),
1391 static_cast<double (*)(double)>(std::exp), tBiotStress, nb_uniq);
1392
1393 tDRe(i, j, k, l) =
1394 t_diff2_stretch_stress(i, j, k, l) - t_model_log_stress_diff(i, j, k, l);
1395
1396 if (tDReMat.size1() != size_symm || tDReMat.size2() != size_symm) {
1397 tDReMat.resize(size_symm, size_symm, false);
1398 }
1399 auto t_d_re_mat = getFTensor2FromMat<size_symm, size_symm>(tDReMat);
1400 t_d_re_mat(L, J) = t_L(i, j, L) * (tDRe(i, j, k, l) * t_L(k, l, J));
1401
1403}
1404
1405template <typename T_Biota, typename T_Stretch>
1406MoFEMErrorCode
1408 T_Stretch>::calculateBiotStretch() {
1410
1412 tBiotStreach(i, j) =
1413 EigenMatrix::getMat(tHenckyStreachEigenVals, tBiotStressEigenVecs,
1414 static_cast<double (*)(double)>(std::exp))(i, j);
1415
1417}
1418
1419template <typename T_Biota, typename T_Stretch>
1421 T_Biota, T_Stretch>::calculateDHenckyStreachDBiotStress() {
1423
1424 CHKERR evaluateFullLhs();
1425
1429
1432 t_eigen_vecs(i, j) = tHenckyStreach(i, j);
1433
1434 CHKERR computeEigenValuesSymmetric(t_eigen_vecs, t_eigen_vals);
1435
1436 const int nb_uniq = getUniqNb<SPACE_DIM>(t_eigen_vals);
1437 if (nb_uniq < SPACE_DIM) {
1438 CHKERR sortEigenVals<SPACE_DIM>(t_eigen_vals, t_eigen_vecs);
1439 }
1440
1441 auto t_diff_stretch = EigenMatrix::getDiffMat(
1442 t_eigen_vals, t_eigen_vecs, static_cast<double (*)(double)>(std::exp),
1443 static_cast<double (*)(double)>(std::exp), nb_uniq);
1444
1445 MatrixDouble d_re_d_biot_stress_mat;
1446 d_re_d_biot_stress_mat.resize(size_symm, size_symm, false);
1447 tDHenckyStreachDBiotStressMat.resize(size_symm, size_symm, false);
1448
1449 auto t_d_re_d_biot_stress =
1450 getFTensor2FromMat<size_symm, size_symm>(d_re_d_biot_stress_mat);
1451 t_d_re_d_biot_stress(L, J) =
1452 t_L(i, j, L) * (t_diff_stretch(k, l, i, j) * t_L(k, l, J));
1453
1454 auto inv_d_re_d_hencky_mat = tDReMat;
1455 CHKERR computeMatrixInverse(inv_d_re_d_hencky_mat);
1456
1457 auto t_inv_d_re_d_hencky =
1458 getFTensor2FromMat<size_symm, size_symm>(inv_d_re_d_hencky_mat);
1459 auto t_d_hencky_d_biot_stress =
1460 getFTensor2FromMat<size_symm, size_symm>(tDHenckyStreachDBiotStressMat);
1461
1462 t_d_hencky_d_biot_stress(L, J) =
1463 -t_inv_d_re_d_hencky(L, M) * t_d_re_d_biot_stress(M, J);
1464
1465 // tDHenckyStreachDBiotStressMat is the packed symmetric operator Q.
1466 // DDg storage is Q W^-1, so remove the repeated off-diagonal contraction
1467 // weights before exposing the same memory as a fourth-order tensor.
1468 auto d_hencky_d_biot_stress_ddg_mat = tDHenckyStreachDBiotStressMat;
1469 for (int row = 0; row != size_symm; ++row) {
1470 for (const int col : {1, 2, 4}) {
1471 d_hencky_d_biot_stress_ddg_mat(row, col) *= 0.5;
1472 }
1473 }
1474 auto get_d_hencky_d_biot_stress_ddg =
1475 MatrixSizeHelper<GetFTensor4DdgFromMatType<SPACE_DIM, SPACE_DIM, -1, DL>,
1476 DL>::size(d_hencky_d_biot_stress_ddg_mat, 1);
1477 auto t_d_hencky_d_biot_stress_ddg = get_d_hencky_d_biot_stress_ddg();
1478 tDHenckyStreachDBiotStress(i, j, k, l) =
1479 t_d_hencky_d_biot_stress_ddg(i, j, k, l);
1480
1482}
1483
1484template <typename T_Biota, typename T_Stretch>
1485MoFEMErrorCode
1488
1489 if (sNes) {
1491 }
1492
1493 tPrincipalDReMat.resize(SPACE_DIM, SPACE_DIM, false);
1494 tDReMat.resize(size_symm, size_symm, false);
1495 tDHenckyStreachDBiotStressMat.resize(size_symm, size_symm, false);
1498 tHenckyStreach(i, j) = 0;
1499 tReVec(i) = 0;
1500 tStretchVec(i) = 0;
1501 tBiotStressEigenVals(i) = 0;
1502 tHenckyStreachEigenVals(i) = 0;
1503 tBiotStressEigenVecs(i, j) = 0;
1504 auto t_principal_d_re =
1505 getFTensor2FromMat<SPACE_DIM, SPACE_DIM>(tPrincipalDReMat);
1506 auto t_d_re = getFTensor2FromMat<size_symm, size_symm>(tDReMat);
1507 auto t_d_hencky_d_biot_stress =
1508 getFTensor2FromMat<size_symm, size_symm>(tDHenckyStreachDBiotStressMat);
1509 t_principal_d_re(i, j) = 0;
1510 t_d_re(L, J) = 0;
1511 t_d_hencky_d_biot_stress(L, J) = 0;
1512
1513 Mat a;
1514 Vec r;
1515 Vec chi;
1516 CHKERR MatCreateSeqDense(PETSC_COMM_SELF, SPACE_DIM, SPACE_DIM, PETSC_NULLPTR,
1517 &a);
1518 CHKERR VecCreateSeq(PETSC_COMM_SELF, SPACE_DIM, &r);
1519 CHKERR VecCreateSeq(PETSC_COMM_SELF, SPACE_DIM, &chi);
1520
1521 A = SmartPetscObj<Mat>(a);
1522 R = SmartPetscObj<Vec>(r);
1523 Chi = SmartPetscObj<Vec>(chi);
1524
1525 sNes = createSNES(PETSC_COMM_SELF);
1526 CHKERR SNESSetType(sNes, SNESNEWTONLS);
1527 CHKERR SNESSetObjective(sNes, snesObjective, this);
1528 CHKERR SNESSetFunction(sNes, R, snesRhs, this);
1529 CHKERR SNESSetJacobian(sNes, A, A, snesLhs, this);
1530
1531 CHKERR PetscOptionsGetReal(PETSC_NULLPTR, PETSC_NULLPTR,
1532 "-neo_hookean_min_eigen_value", &minimEigenValue,
1533 PETSC_NULLPTR);
1534 if (!std::isfinite(minimEigenValue) || minimEigenValue < 0) {
1535 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
1536 "-neo_hookean_min_eigen_value must be finite and non-negative");
1537 }
1538
1539 KSP ksp;
1540 PC pc;
1541 SNESLineSearch line_search;
1542 CHKERR SNESGetKSP(sNes, &ksp);
1543 CHKERR KSPGetPC(ksp, &pc);
1544 CHKERR KSPSetType(ksp, KSPPREONLY);
1545 CHKERR PCSetType(pc, PCLU);
1546 CHKERR SNESSetTolerances(sNes, tol, tol, tol, max_iter, PETSC_DEFAULT);
1547 CHKERR SNESGetLineSearch(sNes, &line_search);
1548 CHKERR SNESLineSearchSetType(line_search, SNESLINESEARCHL2);
1549 PetscReal min_lambda;
1550 PetscReal max_step;
1551 PetscReal line_search_rtol;
1552 PetscReal line_search_atol;
1553 PetscReal line_search_ltol;
1554 PetscInt line_search_max_it;
1555 CHKERR SNESLineSearchGetTolerances(line_search, &min_lambda, &max_step,
1556 &line_search_rtol, &line_search_atol,
1557 &line_search_ltol, &line_search_max_it);
1558 CHKERR SNESLineSearchSetTolerances(line_search, min_lambda, 1.,
1559 line_search_rtol, line_search_atol,
1560 line_search_ltol, line_search_max_it);
1561 CHKERR SNESAppendOptionsPrefix(sNes, "nh_stretch_");
1562 CHKERR SNESSetFromOptions(sNes);
1563
1565}
1566
1567template <typename T_Biota, typename T_Stretch>
1568MoFEMErrorCode
1570 SNES, Vec x, PetscReal *objective, void *ctx) {
1572
1573 auto stretch_from_stress =
1575
1576 const double *x_array;
1577 CHKERR VecGetArrayRead(x, &x_array);
1578 for (int ii = 0; ii != SPACE_DIM; ++ii) {
1579 stretch_from_stress->tStretchVec(ii) = x_array[ii];
1580 }
1581 CHKERR VecRestoreArrayRead(x, &x_array);
1582
1583 *objective = stretch_from_stress->evaluateComplementaryPotential();
1584 if (!std::isfinite(*objective)) {
1585 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FP,
1586 "Non-finite Neo-Hookean complementary potential");
1587 }
1588
1590}
1591
1592template <typename T_Biota, typename T_Stretch>
1593MoFEMErrorCode
1595 SNES, Vec x, Vec r, void *ctx) {
1597
1598 auto stretch_from_stress =
1600
1601 const double *x_array;
1602 CHKERR VecGetArrayRead(x, &x_array);
1603 for (int ii = 0; ii != SPACE_DIM; ++ii) {
1604 stretch_from_stress->tStretchVec(ii) = x_array[ii];
1605 }
1606 CHKERR VecRestoreArrayRead(x, &x_array);
1607
1608 CHKERR stretch_from_stress->evaluateRhs();
1609
1610 double *r_array;
1611 CHKERR VecGetArray(r, &r_array);
1612 for (int ii = 0; ii != SPACE_DIM; ++ii) {
1613 // R = d(S:U-W)/dh, therefore -R is the gradient of W-S:U.
1614 r_array[ii] = -stretch_from_stress->tReVec(ii);
1615 }
1616 CHKERR VecRestoreArray(r, &r_array);
1617
1619}
1620
1621template <typename T_Biota, typename T_Stretch>
1622MoFEMErrorCode
1624 SNES, Vec x, Mat A, Mat B, void *ctx) {
1626
1627 auto stretch_from_stress =
1629
1630 const double *x_array;
1631 CHKERR VecGetArrayRead(x, &x_array);
1632 for (int ii = 0; ii != SPACE_DIM; ++ii) {
1633 stretch_from_stress->tStretchVec(ii) = x_array[ii];
1634 }
1635 CHKERR VecRestoreArrayRead(x, &x_array);
1636
1637 CHKERR stretch_from_stress->evaluateLhs();
1638
1639 PetscScalar *a_array;
1640 CHKERR MatDenseGetArray(A, &a_array);
1641 for (int aa = 0; aa != SPACE_DIM; ++aa) {
1642 for (int bb = 0; bb != SPACE_DIM; ++bb) {
1643 // Use the symmetric complementary Hessian. evaluateLhs() optionally
1644 // projects this Hessian before restoring the residual-Jacobian sign.
1645 a_array[aa + SPACE_DIM * bb] =
1646 -0.5 * (stretch_from_stress->tPrincipalDReMat(aa, bb) +
1647 stretch_from_stress->tPrincipalDReMat(bb, aa));
1648 }
1649 }
1650 CHKERR MatDenseRestoreArray(A, &a_array);
1651
1652 CHKERR MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY);
1653 CHKERR MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY);
1654 if (A != B) {
1655 CHKERR MatCopy(A, B, SAME_NONZERO_PATTERN);
1656 }
1657
1659}
1660
1661template <typename T_Biota, typename T_Stretch>
1662MoFEMErrorCode
1664 const PetscErrorCode snes_solve_error,
1665 const PetscErrorCode accepted_state_error,
1666 const PetscErrorCode reason_query_error) {
1668
1669 // Failure reporting must never replace the error which triggered it. Keep
1670 // every query best-effort and report the first diagnostic error.
1671 PetscErrorCode diagnostic_error = 0;
1672 auto record_diagnostic_error = [&](const PetscErrorCode error) {
1673 if (error && !diagnostic_error) {
1674 diagnostic_error = error;
1675 }
1676 return error == 0;
1677 };
1678
1679 SNESConvergedReason snes_reason = SNES_CONVERGED_ITERATING;
1680 PetscInt snes_iterations = -1;
1681 PetscReal function_norm = std::numeric_limits<PetscReal>::quiet_NaN();
1682 record_diagnostic_error(SNESGetConvergedReason(sNes, &snes_reason));
1683 record_diagnostic_error(SNESGetIterationNumber(sNes, &snes_iterations));
1684 record_diagnostic_error(SNESGetFunctionNorm(sNes, &function_norm));
1685
1686 PetscMPIInt rank = -1;
1687 record_diagnostic_error(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
1688
1689 std::ostringstream atom_reproducer;
1690 atom_reproducer << std::scientific
1691 << std::setprecision(
1692 std::numeric_limits<double>::max_digits10)
1693 << "./neohookean_stretch_gradient_atom"
1694 << " -nh_stretch_atom_c10 " << c10 << " -nh_stretch_atom_k "
1695 << K << " -nh_stretch_atom_q " << externalPressure
1696 << " -nh_stretch_atom_max_it " << max_iter
1697 << " -nh_stretch_atom_tol " << tol
1698 << " -nh_stretch_atom_biot_stress " << tBiotStress(0, 0)
1699 << "," << tBiotStress(0, 1) << "," << tBiotStress(0, 2) << ","
1700 << tBiotStress(1, 1) << "," << tBiotStress(1, 2) << ","
1701 << tBiotStress(2, 2);
1702
1703 // setUPSnes() gives the atom the same defaults as production. Append only
1704 // explicitly supplied options which can change this local solve.
1705 const char *const reproduced_options[] = {
1706 "-neo_hookean_min_eigen_value",
1707 "-nh_stretch_test_zero_initial_guess",
1708 "-nh_stretch_snes_type",
1709 "-nh_stretch_snes_linesearch_type",
1710 "-nh_stretch_snes_linesearch_minlambda",
1711 "-nh_stretch_snes_linesearch_maxstep",
1712 "-nh_stretch_snes_linesearch_rtol",
1713 "-nh_stretch_snes_linesearch_atol",
1714 "-nh_stretch_snes_linesearch_ltol",
1715 "-nh_stretch_snes_linesearch_max_it",
1716 "-nh_stretch_snes_linesearch_damping",
1717 "-nh_stretch_snes_linesearch_order",
1718 "-nh_stretch_snes_linesearch_alpha",
1719 "-nh_stretch_ksp_type",
1720 "-nh_stretch_ksp_rtol",
1721 "-nh_stretch_ksp_atol",
1722 "-nh_stretch_ksp_divtol",
1723 "-nh_stretch_ksp_max_it",
1724 "-nh_stretch_ksp_gmres_restart",
1725 "-nh_stretch_pc_type",
1726 "-nh_stretch_snes_atol",
1727 "-nh_stretch_snes_rtol",
1728 "-nh_stretch_snes_stol",
1729 "-nh_stretch_snes_max_it",
1730 "-nh_stretch_snes_max_funcs",
1731 "-nh_stretch_snes_error_if_not_converged"};
1732 for (const auto option_name : reproduced_options) {
1733 char option_value[PETSC_MAX_PATH_LEN] = {};
1734 PetscBool option_set = PETSC_FALSE;
1735 if (record_diagnostic_error(PetscOptionsGetString(
1736 PETSC_NULLPTR, PETSC_NULLPTR, option_name, option_value,
1737 sizeof(option_value), &option_set)) &&
1738 option_set) {
1739 atom_reproducer << " " << option_name;
1740 if (option_value[0]) {
1741 atom_reproducer << " " << option_value;
1742 }
1743 }
1744 }
1745 atom_reproducer << " -log_no_color";
1746
1747 // MoFEM's PETSc log formatter has a fixed 1024-byte buffer. Bypass it for
1748 // this one potentially long record so the copy-paste command is never cut.
1749 const auto atom_reproducer_string = atom_reproducer.str();
1750 std::ostringstream atom_reproducer_log;
1751 atom_reproducer_log << "[" << rank << "] <warning> atom_reproducer_command="
1752 << atom_reproducer_string << '\n';
1753 const auto atom_reproducer_log_string = atom_reproducer_log.str();
1754 std::fwrite(atom_reproducer_log_string.data(), 1,
1755 atom_reproducer_log_string.size(), stderr);
1756 std::fflush(stderr);
1757
1758 const char *reason_string = "unavailable";
1759 record_diagnostic_error(SNESGetConvergedReasonString(sNes, &reason_string));
1760 MOFEM_LOG("SELF", Sev::warning)
1761 << std::scientific
1762 << std::setprecision(std::numeric_limits<double>::max_digits10)
1763 << "Neo-Hookean principal SNES failed: reason=" << reason_string << " ("
1764 << static_cast<int>(snes_reason) << ")"
1765 << " iterations=" << snes_iterations << " fnorm=" << function_norm
1766 << " errors=[" << snes_solve_error << "," << accepted_state_error << ","
1767 << reason_query_error << "," << diagnostic_error << "]";
1768
1770}
1771
1772template <typename T_Biota, typename T_Stretch>
1773MoFEMErrorCode
1775 T_Stretch>::calculateStretch() {
1777
1778 if (!sNes) {
1779 SETERRQ(PETSC_COMM_SELF, MOFEM_DATA_INCONSISTENCY,
1780 "Neo-Hookean stretch-from-stress SNES is not set up");
1781 }
1782 CHKERR validateMaterialParameters(c10, K, "stretch-from-stress solve");
1783
1785 CHKERR computeEigenValuesSymmetric(tBiotStress, tBiotStressEigenVals,
1786 tBiotStressEigenVecs);
1787
1788 FTensor::Tensor1<double, SPACE_DIM> t_target_biot_stress_eigenvalues;
1789 t_target_biot_stress_eigenvalues(i) = tBiotStressEigenVals(i);
1790
1791 constexpr auto t_one = FTensor::One<>();
1792 const double tr_biot_stress =
1793 tBiotStressEigenVals(i) * t_one(i) + SPACE_DIM * externalPressure;
1794 auto set_linear_initial_guess = [&]() {
1795 tStretchVec(i) =
1796 (tBiotStressEigenVals(i) + externalPressure - tr_biot_stress / 3.) /
1797 (4 * c10) +
1798 tr_biot_stress / (9 * K);
1799 };
1800 set_linear_initial_guess();
1801 PetscBool test_zero_initial_guess = PETSC_FALSE;
1802 CHKERR PetscOptionsHasName(PETSC_NULLPTR, PETSC_NULLPTR,
1803 "-nh_stretch_test_zero_initial_guess",
1804 &test_zero_initial_guess);
1805 if (test_zero_initial_guess) {
1806 tStretchVec(i) = 0;
1807 }
1808 struct LocalSolveState {
1809 PetscErrorCode solveError = 0;
1810 PetscErrorCode acceptedStateError = 0;
1811 PetscErrorCode reasonQueryError = 0;
1812 PetscErrorCode residualError = 0;
1813 PetscErrorCode hessianError = 0;
1814 SNESConvergedReason reason = SNES_CONVERGED_ITERATING;
1815 double residualNorm = std::numeric_limits<double>::infinity();
1816 bool finiteIterate = false;
1817 bool boundedIterate = false;
1818 bool stableHessian = false;
1819 bool acceptable = false;
1820 };
1821
1822 auto solve_from_current_guess = [&]() {
1823 LocalSolveState state;
1824 double initial_residual_norm = std::numeric_limits<double>::infinity();
1825 if (!evaluateRhs()) {
1826 initial_residual_norm = tReVec.l2();
1827 }
1828
1829 double *chi_array = PETSC_NULLPTR;
1830 state.acceptedStateError = VecGetArray(Chi, &chi_array);
1831 if (!state.acceptedStateError) {
1832 for (int aa = 0; aa != SPACE_DIM; ++aa) {
1833 chi_array[aa] = tStretchVec(aa);
1834 }
1835 state.acceptedStateError = VecRestoreArray(Chi, &chi_array);
1836 }
1837 if (!state.acceptedStateError) {
1838 state.acceptedStateError = VecAssemblyBegin(Chi);
1839 }
1840 if (!state.acceptedStateError) {
1841 state.acceptedStateError = VecAssemblyEnd(Chi);
1842 }
1843 if (state.acceptedStateError) {
1844 return state;
1845 }
1846
1847 state.solveError = SNESSolve(sNes, PETSC_NULLPTR, Chi);
1848
1849 // The callbacks can finish at a rejected trial point. Copy Chi and
1850 // explicitly re-evaluate the residual and unprojected Hessian there.
1851 const double *accepted_chi_array = PETSC_NULLPTR;
1852 state.acceptedStateError = VecGetArrayRead(Chi, &accepted_chi_array);
1853 if (!state.acceptedStateError) {
1854 for (int aa = 0; aa != SPACE_DIM; ++aa) {
1855 tStretchVec(aa) = accepted_chi_array[aa];
1856 }
1857 state.acceptedStateError = VecRestoreArrayRead(Chi, &accepted_chi_array);
1858 }
1859 state.reasonQueryError = SNESGetConvergedReason(sNes, &state.reason);
1860 if (!state.acceptedStateError) {
1861 state.residualError = evaluateRhs();
1862 state.hessianError = evaluateLhs();
1863 }
1864
1865 state.finiteIterate = !state.acceptedStateError;
1866 double maximum_absolute_natural_hencky = 0.;
1867 for (int aa = 0; aa != SPACE_DIM && state.finiteIterate; ++aa) {
1868 state.finiteIterate = std::isfinite(tStretchVec(aa));
1869 maximum_absolute_natural_hencky =
1870 std::max(maximum_absolute_natural_hencky, std::abs(tStretchVec(aa)));
1871 }
1872 constexpr double catastrophic_hencky_limit = 10. * EshelbianCore::v_max;
1873 state.boundedIterate =
1874 state.finiteIterate &&
1875 maximum_absolute_natural_hencky <= catastrophic_hencky_limit;
1876
1877 if (!state.residualError) {
1878 state.residualNorm = tReVec.l2();
1879 }
1880
1881 const double stability_tolerance =
1882 std::sqrt(std::numeric_limits<double>::epsilon()) *
1883 std::max({1., std::abs(K), complementaryHessianSpectralRadius});
1884 state.stableHessian =
1885 !state.hessianError &&
1886 std::isfinite(minimumComplementaryHessianEigenvalue) &&
1887 minimumComplementaryHessianEigenvalue > stability_tolerance;
1888
1889 PetscReal absolute_tolerance = tol;
1890 PetscReal relative_tolerance = tol;
1891 PetscReal step_tolerance = PETSC_DEFAULT;
1892 PetscInt maximum_iterations = max_iter;
1893 PetscInt maximum_function_evaluations = PETSC_DEFAULT;
1894 const PetscErrorCode tolerance_error = SNESGetTolerances(
1895 sNes, &absolute_tolerance, &relative_tolerance, &step_tolerance,
1896 &maximum_iterations, &maximum_function_evaluations);
1897 const double acceptable_residual =
1898 tolerance_error
1899 ? 10. * tol
1900 : 10. * std::max(static_cast<double>(absolute_tolerance),
1901 static_cast<double>(relative_tolerance) *
1902 initial_residual_norm);
1903 const bool converged =
1904 !state.reasonQueryError && state.reason > SNES_CONVERGED_ITERATING;
1905 const bool mildly_nonconverged = allowNonConverged &&
1906 !state.reasonQueryError &&
1907 state.reason == SNES_DIVERGED_MAX_IT &&
1908 state.residualNorm <= acceptable_residual;
1909 const bool admissible_error =
1910 !state.solveError || state.solveError == PETSC_ERR_NOT_CONVERGED;
1911 state.acceptable =
1912 admissible_error && !state.acceptedStateError && !state.residualError &&
1913 !state.hessianError && state.finiteIterate && state.boundedIterate &&
1914 state.stableHessian && (converged || mildly_nonconverged);
1915 return state;
1916 };
1917
1918 struct ContinuationState {
1919 LocalSolveState finalState;
1920 double acceptedFraction = 0.;
1921 double rejectedFraction = 0.;
1922 FTensor::Tensor1<double, SPACE_DIM> tRejectedHencky{0., 0., 0.};
1923 };
1924
1925 auto run_stress_continuation = [&]() {
1926 ContinuationState continuation;
1927 FTensor::Tensor1<double, SPACE_DIM> t_accepted_hencky{0., 0., 0.};
1928 double fraction_step = 0.25;
1929 constexpr double minimum_fraction_step = 1.e-6;
1930 constexpr int maximum_continuation_attempts = 200;
1931
1932 for (int attempt = 0; attempt != maximum_continuation_attempts &&
1933 continuation.acceptedFraction < 1.;
1934 ++attempt) {
1935 const double trial_fraction =
1936 std::min(1., continuation.acceptedFraction + fraction_step);
1937 tBiotStressEigenVals(i) =
1938 trial_fraction * t_target_biot_stress_eigenvalues(i);
1939 tStretchVec(i) = t_accepted_hencky(i);
1940
1941 continuation.finalState = solve_from_current_guess();
1942 if (continuation.finalState.acceptable) {
1943 continuation.acceptedFraction = trial_fraction;
1944 t_accepted_hencky(i) = tStretchVec(i);
1945 fraction_step =
1946 std::min(1. - continuation.acceptedFraction, 1.5 * fraction_step);
1947 } else {
1948 continuation.rejectedFraction = trial_fraction;
1949 continuation.tRejectedHencky(i) = tStretchVec(i);
1950 fraction_step *= 0.5;
1951 tStretchVec(i) = t_accepted_hencky(i);
1952 }
1953
1954 if (continuation.acceptedFraction < 1. &&
1955 fraction_step < minimum_fraction_step) {
1956 break;
1957 }
1958 }
1959
1960 tBiotStressEigenVals(i) = t_target_biot_stress_eigenvalues(i);
1961 return continuation;
1962 };
1963
1964 auto direct_state = solve_from_current_guess();
1965 auto final_state = direct_state;
1966 bool used_continuation = false;
1967
1968 if (!direct_state.acceptable) {
1969 used_continuation = true;
1970 const auto original_continuation = run_stress_continuation();
1971 final_state = original_continuation.finalState;
1972
1973 if (original_continuation.acceptedFraction < 1.) {
1974 const double failure_minimum_hessian_eigenvalue =
1975 minimumComplementaryHessianEigenvalue;
1976 (void)logSnesFailure(final_state.solveError,
1977 final_state.acceptedStateError,
1978 final_state.reasonQueryError);
1979 const double reached_fraction = original_continuation.acceptedFraction;
1980 MOFEM_LOG("EP", Sev::warning)
1981 << "Neo-Hookean stress continuation stopped at accepted fraction "
1982 << reached_fraction << " after rejecting fraction "
1983 << original_continuation.rejectedFraction
1984 << "; residual = " << final_state.residualNorm
1985 << "; minimum complementary Hessian eigenvalue = "
1986 << failure_minimum_hessian_eigenvalue
1987 << "; rejected principal Hencky = ["
1988 << original_continuation.tRejectedHencky(0) << ","
1989 << original_continuation.tRejectedHencky(1) << ","
1990 << original_continuation.tRejectedHencky(2) << "]";
1991 MOFEM_LOG("EP", Sev::error)
1992 << "Abaqus Neo-Hookean stretch-from-stress solve failed to reach a "
1993 "globally unique constitutive target; this indicates a numerical "
1994 "range or nonlinear-solver failure; aborting all MPI ranks";
1995 // This failure is detected by one integration-point owner only. Returning
1996 // a rank-local PETSc error lets that rank unwind distributed solvers
1997 // while the other ranks remain in residual assembly, producing mismatched
1998 // MPI collectives (for example MUMPS MPI_Bcast versus Vec MPI_Allreduce).
1999 // Abort before rank-asymmetric destruction of distributed PETSc objects.
2000 (void)MPI_Abort(PETSC_COMM_WORLD, MOFEM_OPERATION_UNSUCCESSFUL);
2001 SETERRQ(PETSC_COMM_SELF, MOFEM_OPERATION_UNSUCCESSFUL,
2002 "Abaqus Neo-Hookean stretch-from-stress numerical solve failed");
2003 }
2004 }
2005
2006 if (used_continuation) {
2007 MOFEM_LOG("EP", Sev::verbose)
2008 << "Neo-Hookean stretch-from-stress recovered by adaptive stress "
2009 "continuation";
2010 } else if (final_state.reason == SNES_DIVERGED_MAX_IT) {
2011 MOFEM_LOG("EP", Sev::warning)
2012 << "Neo-Hookean stretch-from-stress accepted a bounded, stable "
2013 "maximum-iteration state with residual "
2014 << final_state.residualNorm;
2015 }
2016
2017 for (int aa = 0; aa != SPACE_DIM; ++aa) {
2018 if (aa != 0 &&
2019 isEq(tBiotStressEigenVals(aa), tBiotStressEigenVals(aa - 1))) {
2020 continue;
2021 }
2022 double group_sum = 0;
2023 int group_size = 0;
2024 for (int bb = aa; bb != SPACE_DIM; ++bb) {
2025 if (isEq(tBiotStressEigenVals(aa), tBiotStressEigenVals(bb))) {
2026 group_sum += tStretchVec(bb);
2027 ++group_size;
2028 }
2029 }
2030 const double group_average = group_sum / group_size;
2031 for (int bb = aa; bb != SPACE_DIM; ++bb) {
2032 if (isEq(tBiotStressEigenVals(aa), tBiotStressEigenVals(bb))) {
2033 tStretchVec(bb) = group_average;
2034 }
2035 }
2036 }
2037
2038 tHenckyStreachEigenVals(i) = tStretchVec(i);
2039 auto identity = [](const double v) { return v; };
2040 tHenckyStreach(i, j) = EigenMatrix::getMat(
2041 tHenckyStreachEigenVals, tBiotStressEigenVecs, identity)(i, j);
2042 CHKERR calculateBiotStretch();
2043
2045}
2046
2049
2050 using StretchFromStress =
2052
2053 PetscReal atom_c10 = 1.7;
2054 PetscReal atom_K = 8.5;
2055 PetscReal atom_q = 0.;
2056 PetscReal atom_tol = 1e-13;
2057 PetscInt atom_max_iter = 100;
2058 std::array<PetscReal, size_symm + 1> atom_biot_stress_input{};
2059 std::array<PetscReal, size_symm> atom_biot_stress{};
2060 PetscInt atom_biot_stress_size = atom_biot_stress_input.size();
2061 PetscBool atom_biot_stress_set = PETSC_FALSE;
2062 CHKERR PetscOptionsGetReal(PETSC_NULLPTR, PETSC_NULLPTR,
2063 "-nh_stretch_atom_c10", &atom_c10, PETSC_NULLPTR);
2064 CHKERR PetscOptionsGetReal(PETSC_NULLPTR, PETSC_NULLPTR, "-nh_stretch_atom_k",
2065 &atom_K, PETSC_NULLPTR);
2066 CHKERR PetscOptionsGetReal(PETSC_NULLPTR, PETSC_NULLPTR, "-nh_stretch_atom_q",
2067 &atom_q, PETSC_NULLPTR);
2068 CHKERR PetscOptionsGetInt(PETSC_NULLPTR, PETSC_NULLPTR,
2069 "-nh_stretch_atom_max_it", &atom_max_iter,
2070 PETSC_NULLPTR);
2071 CHKERR PetscOptionsGetReal(PETSC_NULLPTR, PETSC_NULLPTR,
2072 "-nh_stretch_atom_tol", &atom_tol, PETSC_NULLPTR);
2073 CHKERR PetscOptionsGetRealArray(
2074 PETSC_NULLPTR, PETSC_NULLPTR, "-nh_stretch_atom_biot_stress",
2075 atom_biot_stress_input.data(), &atom_biot_stress_size,
2076 &atom_biot_stress_set);
2077 if (!std::isfinite(atom_c10) || atom_c10 <= 0. || !std::isfinite(atom_K) ||
2078 !std::isfinite(atom_q) || !std::isfinite(atom_tol) || atom_tol <= 0. ||
2079 atom_max_iter < 0 ||
2080 atom_max_iter > static_cast<PetscInt>(std::numeric_limits<int>::max())) {
2081 SETERRQ(PETSC_COMM_SELF, MOFEM_INVALID_DATA,
2082 "Invalid Neo-Hookean atom material or SNES parameters");
2083 }
2085 "Neo-Hookean atom test");
2086 if (atom_biot_stress_set && atom_biot_stress_size != size_symm) {
2087 SETERRQ(PETSC_COMM_SELF, MOFEM_INVALID_DATA,
2088 "-nh_stretch_atom_biot_stress needs exactly six values in "
2089 "order s00,s01,s02,s11,s12,s22");
2090 }
2091 if (atom_biot_stress_set) {
2092 for (int rr = 0; rr != size_symm; ++rr) {
2093 const auto value = atom_biot_stress_input[rr];
2094 if (!std::isfinite(value)) {
2095 SETERRQ(PETSC_COMM_SELF, MOFEM_INVALID_DATA,
2096 "-nh_stretch_atom_biot_stress values must be finite");
2097 }
2098 atom_biot_stress[rr] = value;
2099 }
2100 }
2101
2103
2104 for (const double coordinate_stretch :
2105 {-2., 0., EshelbianCore::v_max, EshelbianCore::v_max + 3.}) {
2106 const double stretch = std::exp(coordinate_stretch);
2107 double recovered_coordinate_stretch = 0.;
2109 stretch, recovered_coordinate_stretch);
2110 const double inverse_error =
2111 std::abs(recovered_coordinate_stretch - coordinate_stretch);
2112 const double inverse_tolerance =
2113 1e-12 * std::max(1., std::abs(coordinate_stretch));
2114 if (!std::isfinite(inverse_error) || inverse_error > inverse_tolerance) {
2115 SETERRQ(PETSC_COMM_SELF, MOFEM_ATOM_TEST_INVALID,
2116 "HMHNeohookean stretch-coordinate inverse check failed");
2117 }
2118 }
2119
2123
2124 auto set_symm_tensor = [&](auto &t_symm, VectorDouble &values) {
2125 auto t_values = getFTensor1FromPtr<size_symm>(&*values.data().begin());
2126 t_symm(i, j) = t_L(i, j, L) * t_values(L);
2127 };
2128
2129 auto get_symm_tensor = [&](auto &t_symm, VectorDouble &values) {
2130 values.resize(size_symm, false);
2131 values[0] = t_symm(0, 0);
2132 values[1] = t_symm(0, 1);
2133 values[2] = t_symm(0, 2);
2134 values[3] = t_symm(1, 1);
2135 values[4] = t_symm(1, 2);
2136 values[5] = t_symm(2, 2);
2137 };
2138
2139 auto set_material = [&](StretchFromStress &stretch_from_stress) {
2140 stretch_from_stress.c10 = atom_c10;
2141 stretch_from_stress.K = atom_K;
2142 stretch_from_stress.externalPressure = atom_q;
2143 stretch_from_stress.max_iter = atom_max_iter;
2144 stretch_from_stress.tol = atom_tol;
2145 };
2146
2147 auto check_snes_solve =
2148 [&](StretchFromStress &stretch_from_stress, const char *case_name,
2149 const int col, const double perturbation, const bool log_values) {
2151
2152 SNESConvergedReason reason;
2153 const char *reason_string = "unknown";
2154 PetscInt iterations;
2155 PetscReal fnorm;
2156 CHKERR SNESGetConvergedReason(stretch_from_stress.sNes, &reason);
2157 CHKERR SNESGetConvergedReasonString(stretch_from_stress.sNes,
2158 &reason_string);
2159 CHKERR SNESGetIterationNumber(stretch_from_stress.sNes, &iterations);
2160 CHKERR SNESGetFunctionNorm(stretch_from_stress.sNes, &fnorm);
2161
2162 const bool exceeded_built_in_iteration_limit =
2163 !atom_biot_stress_set && iterations > 25;
2164 const bool inaccurate_built_in_solution =
2165 !atom_biot_stress_set && fnorm > 1e-10;
2166 if (reason <= 0 || !std::isfinite(fnorm) ||
2167 inaccurate_built_in_solution || exceeded_built_in_iteration_limit) {
2168 MOFEM_LOG("EP", Sev::error)
2169 << "HMHNeohookean principal SNES failed: case=" << case_name
2170 << " col=" << col << " perturbation=" << perturbation
2171 << " iterations=" << iterations << " fnorm=" << fnorm
2172 << " reason=" << static_cast<int>(reason) << " (" << reason_string
2173 << ")";
2174 SETERRQ(PETSC_COMM_SELF, MOFEM_ATOM_TEST_INVALID,
2175 "HMHNeohookean principal stretch SNES did not converge");
2176 }
2177
2178 if (log_values) {
2179 MOFEM_LOG("EP", Sev::inform)
2180 << "HMHNeohookean principal SNES solved: case=" << case_name
2181 << " iterations=" << iterations << " fnorm=" << fnorm
2182 << " reason=" << static_cast<int>(reason) << " (" << reason_string
2183 << ")\nbiot_stress=" << stretch_from_stress.tBiotStress
2184 << "\nhencky_stretch=" << stretch_from_stress.tHenckyStreach
2185 << "\nbiot_stretch=" << stretch_from_stress.tBiotStreach;
2186 }
2187
2189 };
2190
2191 auto solve_for_hencky = [&](VectorDouble &stress_vec,
2192 VectorDouble &hencky_vec, const char *case_name,
2193 const int col, const double perturbation) {
2195
2196 StretchFromStress stretch_from_stress;
2197 set_material(stretch_from_stress);
2198 CHKERR stretch_from_stress.setUPSnes();
2199 set_symm_tensor(stretch_from_stress.tBiotStress, stress_vec);
2200 CHKERR stretch_from_stress.calculateStretch();
2201 CHKERR check_snes_solve(stretch_from_stress, case_name, col, perturbation,
2202 false);
2203 get_symm_tensor(stretch_from_stress.tHenckyStreach, hencky_vec);
2204
2206 };
2207
2208 struct StretchCase {
2209 const char *name;
2210 std::array<double, size_symm> stress;
2211 std::array<double, size_symm> expected_hencky;
2212 bool check_expected;
2213 bool check_zero_tangent;
2214 double tangent_tolerance;
2215 };
2216
2217 const std::array<StretchCase, 8> built_in_stretch_cases{{
2218 {"generic",
2219 {0.18, -0.035, 0.024, -0.095, 0.041, 0.13},
2220 {},
2221 false,
2222 false,
2223 5e-5},
2224 {"zero", {0., 0., 0., 0., 0., 0.}, {}, true, true, 5e-5},
2225 {"hydrostatic",
2226 {3.6322073565896487, 0., 0., 3.6322073565896487, 0., 3.6322073565896487},
2227 {0.1, 0., 0., 0.1, 0., 0.1},
2228 true,
2229 false,
2230 5e-5},
2231 {"rotated_repeated",
2232 {0.09259999671274205, -0.73713612813466134, -0.36856806406733089,
2233 0.092599996712742161, 0.368568064067331, -0.4602520993882544},
2234 {0.02666666666666665, -0.1066666666666666, -0.05333333333333333,
2235 0.02666666666666666, 0.05333333333333333, -0.05333333333333334},
2236 true,
2237 false,
2238 5e-5},
2239 {"rotated_near_repeated",
2240 {0.092600176677642032, -0.73713594355526935, -0.36856807329630148,
2241 0.092600112074853747, 0.36856820250187705, -0.46025239471529633},
2242 {0.02666669266666667, -0.10666664, -0.05333333466666666,
2243 0.02666668333333332, 0.05333335333333333, -0.05333337600000001},
2244 true,
2245 false,
2246 1e-4},
2247 {"rotated_distinct",
2248 {0.8407464902540408, 0.82227899319182596, 0.28074304918139947,
2249 0.75776693280844076, -0.1147839342902007, 0.66914993941471534},
2250 {0.044, 0.14, 0.048, 0.03, -0.02, 0.016},
2251 true,
2252 false,
2253 5e-5},
2254 {"tensile_distortional",
2255 {3.037800922108802, 0., 0., 1.1493777171719499, 0., 1.7405883229709418},
2256 {0.3, 0., 0., -0.12, 0., 0.02},
2257 true,
2258 false,
2259 5e-5},
2260 {"compressive_distortional",
2261 {-3.6858407112404157, 0., 0., 0.067487441652110958, 0.,
2262 -1.1528550088055771},
2263 {-0.3, 0., 0., 0.12, 0., -0.02},
2264 true,
2265 false,
2266 5e-5},
2267 }};
2268
2269 std::vector<StretchCase> stretch_cases;
2270 if (atom_biot_stress_set) {
2271 StretchCase command_line_case{"command_line", {}, {}, false, false, 5e-5};
2272 for (int rr = 0; rr != size_symm; ++rr) {
2273 command_line_case.stress[rr] = atom_biot_stress[rr];
2274 }
2275 stretch_cases.push_back(command_line_case);
2276 } else {
2277 stretch_cases.assign(built_in_stretch_cases.begin(),
2278 built_in_stretch_cases.end());
2279 }
2280 const bool use_reference_configuration =
2281 atom_c10 == 1.7 && atom_K == 8.5 && atom_q == 0.;
2282
2283 auto make_vector = [](const std::array<double, size_symm> &values) {
2284 VectorDouble vector;
2285 vector.resize(size_symm, false);
2286 for (int rr = 0; rr != size_symm; ++rr) {
2287 vector[rr] = values[rr];
2288 }
2289 return vector;
2290 };
2291
2292 auto commutator_error = [](auto &t_a, auto &t_b) {
2293 double commutator_norm_squared = 0.;
2294 double a_norm_squared = 0.;
2295 double b_norm_squared = 0.;
2296 for (int rr = 0; rr != SPACE_DIM; ++rr) {
2297 for (int cc = 0; cc != SPACE_DIM; ++cc) {
2298 double commutator = 0.;
2299 for (int kk = 0; kk != SPACE_DIM; ++kk) {
2300 commutator += t_a(rr, kk) * t_b(kk, cc) - t_b(rr, kk) * t_a(kk, cc);
2301 }
2302 commutator_norm_squared += commutator * commutator;
2303 a_norm_squared += t_a(rr, cc) * t_a(rr, cc);
2304 b_norm_squared += t_b(rr, cc) * t_b(rr, cc);
2305 }
2306 }
2307 return std::sqrt(commutator_norm_squared) /
2308 std::max(1., std::sqrt(a_norm_squared * b_norm_squared));
2309 };
2310
2311 constexpr double eps = 1e-6;
2312 for (const auto &stretch_case : stretch_cases) {
2313 auto stress_vec = make_vector(stretch_case.stress);
2314
2315 StretchFromStress base_stretch_from_stress;
2316 set_material(base_stretch_from_stress);
2317 CHKERR base_stretch_from_stress.setUPSnes();
2318 set_symm_tensor(base_stretch_from_stress.tBiotStress, stress_vec);
2319 CHKERR base_stretch_from_stress.calculateStretch();
2320 CHKERR check_snes_solve(base_stretch_from_stress, stretch_case.name, -1, 0.,
2321 true);
2322 CHKERR base_stretch_from_stress.calculateDHenckyStreachDBiotStress();
2323
2324 VectorDouble base_hencky_vec;
2325 get_symm_tensor(base_stretch_from_stress.tHenckyStreach, base_hencky_vec);
2326
2327 for (int rr = 0; rr != SPACE_DIM; ++rr) {
2328 for (int cc = rr; cc != SPACE_DIM; ++cc) {
2329 if (!std::isfinite(base_stretch_from_stress.tBiotStress(rr, cc)) ||
2330 !std::isfinite(base_stretch_from_stress.tHenckyStreach(rr, cc)) ||
2331 !std::isfinite(base_stretch_from_stress.tBiotStreach(rr, cc))) {
2332 SETERRQ(PETSC_COMM_SELF, MOFEM_ATOM_TEST_INVALID,
2333 "HMHNeohookean spectral reconstruction is not finite");
2334 }
2335 }
2336 }
2337
2338 if (atom_q == 0. && stretch_case.check_expected &&
2339 (use_reference_configuration || stretch_case.check_zero_tangent)) {
2340 double max_expected_error = 0.;
2341 for (int rr = 0; rr != size_symm; ++rr) {
2342 const double expected_error =
2343 std::abs(base_hencky_vec[rr] - stretch_case.expected_hencky[rr]);
2344 if (!std::isfinite(expected_error)) {
2345 SETERRQ(PETSC_COMM_SELF, MOFEM_ATOM_TEST_INVALID,
2346 "HMHNeohookean spectral reconstruction is not finite");
2347 }
2348 max_expected_error = std::max(max_expected_error, expected_error);
2349 }
2350 MOFEM_LOG("EP", Sev::inform)
2351 << "HMHNeohookean spectral reconstruction check: case="
2352 << stretch_case.name << " max_abs=" << max_expected_error;
2353 if (max_expected_error > 1e-9) {
2354 SETERRQ(PETSC_COMM_SELF, MOFEM_ATOM_TEST_INVALID,
2355 "HMHNeohookean spectral reconstruction check failed");
2356 }
2357 }
2358
2359 const double stress_hencky_commutator =
2360 commutator_error(base_stretch_from_stress.tBiotStress,
2361 base_stretch_from_stress.tHenckyStreach);
2362 const double stress_biot_commutator =
2363 commutator_error(base_stretch_from_stress.tBiotStress,
2364 base_stretch_from_stress.tBiotStreach);
2365 const double hencky_biot_commutator =
2366 commutator_error(base_stretch_from_stress.tHenckyStreach,
2367 base_stretch_from_stress.tBiotStreach);
2368 const double max_commutator =
2369 std::max(stress_hencky_commutator,
2370 std::max(stress_biot_commutator, hencky_biot_commutator));
2371 if (!std::isfinite(max_commutator) || max_commutator > 1e-10) {
2372 SETERRQ(PETSC_COMM_SELF, MOFEM_ATOM_TEST_INVALID,
2373 "HMHNeohookean coaxial tensor check failed");
2374 }
2375
2376 FTensor::Tensor1<double, SPACE_DIM> t_hencky_eigen_vals;
2377 FTensor::Tensor1<double, SPACE_DIM> t_biot_eigen_vals;
2379 t_eigen_vecs(i, j) = base_stretch_from_stress.tHenckyStreach(i, j);
2380 CHKERR computeEigenValuesSymmetric(t_eigen_vecs, t_hencky_eigen_vals);
2381 auto t_expected_biot_stretch =
2382 EigenMatrix::getMat(t_hencky_eigen_vals, t_eigen_vecs,
2383 static_cast<double (*)(double)>(std::exp));
2384 for (int rr = 0; rr != SPACE_DIM; ++rr) {
2385 for (int cc = rr; cc != SPACE_DIM; ++cc) {
2386 const double biot_stretch_error =
2387 std::abs(base_stretch_from_stress.tBiotStreach(rr, cc) -
2388 t_expected_biot_stretch(rr, cc));
2389 if (!std::isfinite(biot_stretch_error) || biot_stretch_error > 1e-10) {
2390 SETERRQ(PETSC_COMM_SELF, MOFEM_ATOM_TEST_INVALID,
2391 "HMHNeohookean Biot stretch does not match the selected "
2392 "stretch map");
2393 }
2394 }
2395 }
2396 t_eigen_vecs(i, j) = base_stretch_from_stress.tBiotStreach(i, j);
2397 CHKERR computeEigenValuesSymmetric(t_eigen_vecs, t_biot_eigen_vals);
2398 for (int aa = 0; aa != SPACE_DIM; ++aa) {
2399 if (!std::isfinite(t_biot_eigen_vals(aa)) ||
2400 t_biot_eigen_vals(aa) <= 0.) {
2401 SETERRQ(PETSC_COMM_SELF, MOFEM_ATOM_TEST_INVALID,
2402 "HMHNeohookean Biot stretch is not positive definite");
2403 }
2404 }
2405
2406 MatrixDouble fd_gradient;
2407 fd_gradient.resize(size_symm, size_symm, false);
2408 for (int col = 0; col != size_symm; ++col) {
2409 auto stress_plus_vec = stress_vec;
2410 auto stress_minus_vec = stress_vec;
2411 stress_plus_vec[col] += eps;
2412 stress_minus_vec[col] -= eps;
2413
2414 VectorDouble plus_hencky_vec;
2415 VectorDouble minus_hencky_vec;
2416 CHKERR solve_for_hencky(stress_plus_vec, plus_hencky_vec,
2417 stretch_case.name, col, eps);
2418 CHKERR solve_for_hencky(stress_minus_vec, minus_hencky_vec,
2419 stretch_case.name, col, -eps);
2420
2421 for (int row = 0; row != size_symm; ++row) {
2422 fd_gradient(row, col) =
2423 (plus_hencky_vec[row] - minus_hencky_vec[row]) / (2. * eps);
2424 }
2425 }
2426
2427 double max_abs_error = 0.;
2428 double max_scaled_error = 0.;
2429 double max_analytical = 0.;
2430 double max_numerical = 0.;
2431 int max_row = 0;
2432 int max_col = 0;
2433 for (int row = 0; row != size_symm; ++row) {
2434 for (int col = 0; col != size_symm; ++col) {
2435 const double analytical =
2436 base_stretch_from_stress.tDHenckyStreachDBiotStressMat(row, col);
2437 const double numerical = fd_gradient(row, col);
2438 if (!std::isfinite(analytical) || !std::isfinite(numerical)) {
2439 SETERRQ(PETSC_COMM_SELF, MOFEM_ATOM_TEST_INVALID,
2440 "HMHNeohookean full tangent is not finite");
2441 }
2442 const double abs_error = std::abs(analytical - numerical);
2443 const double scale =
2444 std::max(1., std::max(std::abs(analytical), std::abs(numerical)));
2445 const double scaled_error = abs_error / scale;
2446
2447 if (scaled_error > max_scaled_error) {
2448 max_scaled_error = scaled_error;
2449 max_abs_error = abs_error;
2450 max_analytical = analytical;
2451 max_numerical = numerical;
2452 max_row = row;
2453 max_col = col;
2454 }
2455 }
2456 }
2457
2458 MOFEM_LOG("EP", Sev::inform)
2459 << "HMHNeohookean full tangent finite difference check: case="
2460 << stretch_case.name << " max_abs=" << max_abs_error
2461 << " max_scaled=" << max_scaled_error
2462 << " analytical=" << max_analytical << " numerical=" << max_numerical
2463 << " at (" << max_row << "," << max_col << ")";
2464
2465 if (max_scaled_error > stretch_case.tangent_tolerance) {
2466 SETERRQ(PETSC_COMM_SELF, MOFEM_ATOM_TEST_INVALID,
2467 "HMHNeohookean full tangent finite difference check failed");
2468 }
2469
2470 if (atom_q == 0. && stretch_case.check_zero_tangent) {
2471 const double a = 1. / (4. * atom_c10);
2472 const double q = 1. / (9. * atom_K) - a / 3.;
2473 MatrixDouble expected_tangent;
2474 expected_tangent.resize(size_symm, size_symm, false);
2475 expected_tangent.clear();
2476 for (const int row : {0, 3, 5}) {
2477 for (const int col : {0, 3, 5}) {
2478 expected_tangent(row, col) = q;
2479 }
2480 expected_tangent(row, row) += a;
2481 }
2482 for (const int shear : {1, 2, 4}) {
2483 expected_tangent(shear, shear) = a;
2484 }
2485
2486 double max_zero_tangent_error = 0.;
2487 for (int row = 0; row != size_symm; ++row) {
2488 for (int col = 0; col != size_symm; ++col) {
2489 max_zero_tangent_error = std::max(
2490 max_zero_tangent_error,
2491 std::abs(base_stretch_from_stress.tDHenckyStreachDBiotStressMat(
2492 row, col) -
2493 expected_tangent(row, col)));
2494 }
2495 }
2496 if (!std::isfinite(max_zero_tangent_error) ||
2497 max_zero_tangent_error > 1e-10) {
2498 SETERRQ(PETSC_COMM_SELF, MOFEM_ATOM_TEST_INVALID,
2499 "HMHNeohookean zero-stress analytical tangent check failed");
2500 }
2501 }
2502 }
2503
2505}
2506
2507} // namespace EshelbianPlasticity
std::string type
Lie algebra implementation.
#define MOFEM_TAG_AND_LOG(channel, severity, tag)
Tag and log in channel.
#define FTENSOR_INDEXES(DIM,...)
#define FTENSOR_INDEX(DIM, I)
constexpr double a
static const double eps
constexpr int SPACE_DIM
Fourth-order symmetrization tensor.
Fourth-order differential tensor symmetric in both index pairs.
Kronecker Delta class symmetric.
Kronecker Delta class.
Stateless vector whose components are all equal to one.
Definition One.hpp:22
Mapping from symmetric tensor indices to packed storage index.
#define CHK_THROW_MESSAGE(err, msg)
Check and throw MoFEM exception.
#define MoFEMFunctionReturnHot(a)
Last executable line of each PETSc function used for error handling. Replaces return()
@ H1
continuous field
Definition definitions.h:85
#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_ATOM_TEST_INVALID
Definition definitions.h:40
@ MOFEM_DATA_INCONSISTENCY
Definition definitions.h:31
@ MOFEM_INVALID_DATA
Definition definitions.h:36
@ MOFEM_NOT_IMPLEMENTED
Definition definitions.h:32
#define MoFEMFunctionReturn(a)
Last executable line of each PETSc function used for error handling. Replaces return()
#define CHKERR
Inline error check.
#define MoFEMFunctionBeginHot
First executable line of each MoFEM function, used for error handling. Final line of MoFEM functions ...
@ R
constexpr auto t_kd
double eta
#define MOFEM_LOG(channel, severity)
Log.
#define MOFEM_LOG_CHANNEL(channel)
Set and reset channel.
FTensor::Index< 'i', SPACE_DIM > i
const double c
speed of light (cm/ns)
const double v
phase velocity of light in medium (cm/ns)
const double n
refractive index of diffusive medium
FTensor::Index< 'J', DIM1 > J
Definition level_set.cpp:30
FTensor::Index< 'l', 3 > l
FTensor::Index< 'j', 3 > j
FTensor::Index< 'k', 3 > k
double tol
auto getMat(A &&t_val, B &&t_vec, Fun< double > f)
Get the Mat object.
auto getDiffMat(A &&t_val, B &&t_vec, Fun< double > f, Fun< double > d_f, const int nb)
Get the Diff Mat object.
auto getDiffDiffMat(A &&t_val, B &&t_vec, Fun< double > f, Fun< double > d_f, Fun< double > dd_f, C &&t_S, const int nb)
Get the Diff Diff Mat object.
MoFEMErrorCode testHMHNeohookeanStretchGradient()
void tetcircumcenter_tp(double a[3], double b[3], double c[3], double d[3], double circumcenter[3], double *xi, double *eta, double *zeta)
EntitiesFieldData::EntData EntData
ForcesAndSourcesCore::UserDataOperator UserDataOperator
static constexpr auto size_symm
boost::shared_ptr< VectorDouble > VectorPtr
constexpr AssemblyType A
double h
constexpr auto field_name
FTensor::Index< 'm', 3 > m
static enum StretchSelector stretchSelector
static constexpr double v_max
static enum StretchHandling stretchHandling
static enum RotSelector rotSelector
static enum RotSelector gradApproximator
static PetscBool physicalTimeFlg
static double currentPhysicalTime
static bool isNoStretch()
Recover the stretch tensor from a prescribed Biot stress.
static MoFEMErrorCode snesRhs(SNES snes, Vec x, Vec r, void *ctx)
static MoFEMErrorCode snesObjective(SNES snes, Vec x, PetscReal *objective, void *ctx)
FTensor::Tensor1< double, SPACE_DIM > tStretchVec
SNES unknown: principal Hencky stretches.
static MoFEMErrorCode snesLhs(SNES snes, Vec x, Mat A, Mat B, void *ctx)
FTensor::Tensor2_symmetric< T_Stretch, 3 > tBiotStreach
MoFEMErrorCode logSnesFailure(const PetscErrorCode snes_solve_error, const PetscErrorCode accepted_state_error, const PetscErrorCode reason_query_error)
CalculateStretchFromStress< double, double > stretchFromStress
MoFEMErrorCode doWork(int side, EntityType type, EntData &data)
OpCalculateStretchFromStress(boost::shared_ptr< DataAtIntegrationPts > data_ptr, boost::shared_ptr< MatrixDouble > strain_ptr, boost::shared_ptr< MatrixDouble > stress_ptr, boost::shared_ptr< HMHNeohookean > neohookean_ptr, VectorPtr external_pressure_ptr)
std::map< std::string, boost::shared_ptr< ScalingMethod > > scalingMethodsMap
OpSpatialPhysicalExternalStrain(const std::string &field_name, boost::shared_ptr< DataAtIntegrationPts > data_ptr, boost::shared_ptr< ExternalStrainVec > external_strain_vec_ptr, std::map< std::string, boost::shared_ptr< ScalingMethod > > smv)
OpSpatialPhysical_du_du(std::string row_field, std::string col_field, boost::shared_ptr< DataAtIntegrationPts > data_ptr, const double alpha)
MoFEMErrorCode integrate(EntData &row_data, EntData &col_data)
OpSpatialPhysical(const std::string &field_name, boost::shared_ptr< DataAtIntegrationPts > data_ptr, const double alpha_u)
VolUserDataOperator * returnOpCalculateExternalPressure(VectorPtr external_pressure_ptr, boost::shared_ptr< ExternalStrainVec > external_strain_vec_ptr, std::map< std::string, boost::shared_ptr< ScalingMethod > > smv) override
UserDataOperator * returnOpJacobian(const bool eval_rhs, const bool eval_lhs, boost::shared_ptr< DataAtIntegrationPts > data_ptr, boost::shared_ptr< PhysicalEquations > physics_ptr)
VolUserDataOperator * returnOpCalculateStretchFromStress(boost::shared_ptr< DataAtIntegrationPts > data_ptr, boost::shared_ptr< PhysicalEquations > physics_ptr, boost::shared_ptr< MatrixDouble > strain_ptr) override
VolUserDataOperator * returnOpCalculateStretchFromStress(boost::shared_ptr< DataAtIntegrationPts > data_ptr, boost::shared_ptr< PhysicalEquations > physics_ptr, boost::shared_ptr< MatrixDouble > strain_ptr, VectorPtr external_pressure_ptr) override
static MoFEMErrorCode validateMaterialParameters(const double c10, const double K, const char *source)
static double getShearModulus(const double c10)
static MoFEMErrorCode getCoordinateStretchFromStretch(const double stretch, double &coordinate_stretch)
static MoFEMErrorCode validatePrincipalState(const PrincipalState &state, const char *source)
static double getAbaqusD1(const double K)
static double getPrincipalCoordinateStressDerivativeAtFixedInvariants(const double c10, const PrincipalState &state, const double v)
virtual VolUserDataOperator * returnOpSpatialPhysical(const std::string &field_name, boost::shared_ptr< DataAtIntegrationPts > data_ptr, const double alpha_u)
std::vector< BlockData > blockData
MoFEMErrorCode extractBlockData(std::vector< const CubitMeshSets * > meshset_vec_ptr, Sev sev)
MoFEMErrorCode extractBlockData(Sev sev)
auto getMaterialParameters(EntityHandle ent)
virtual VolUserDataOperator * returnOpSpatialPhysicalExternalStrain(const std::string &field_name, boost::shared_ptr< DataAtIntegrationPts > data_ptr, boost::shared_ptr< ExternalStrainVec > external_strain_vec_ptr, std::map< std::string, boost::shared_ptr< ScalingMethod > > smv)
HMHNeohookean(MoFEM::Interface &m_field, const double c10, const double K)
VolUserDataOperator * returnOpSpatialPhysical_du_du(std::string row_field, std::string col_field, boost::shared_ptr< DataAtIntegrationPts > data_ptr, const double alpha)
static double getLogJacobian(T &principal_coordinate_stretches)
static double getStrainEnergy(const double c10, const double K, T &principal_coordinate_stretches)
static double getPrincipalCoordinateStress(const double c10, const PrincipalState &state, const double v)
static PrincipalState getPrincipalState(const double K, T &principal_coordinate_stretches)
static auto exp(A &&t_w_vee, B &&theta)
Definition Lie.hpp:69
virtual moab::Interface & get_moab()=0
bool sYmm
If true assume that matrix is symmetric structure.
Deprecated interface functions.
Data on single entity (This is passed as argument to DataOperator::doWork)
EntityHandle getFEEntityHandle() const
Return finite element entity handle.
const FEMethod * getFEMethod() const
Return raw pointer to Finite Element Method object.
PetscReal ts_t
Current time value.
MoFEMErrorCode getInterface(IFACE *&iface) const
Get interface reference to pointer of interface.
Calculate q = 3 K_ext epsilon_ext at integration points.
double scale
Definition plastic.cpp:124
double zeta
Viscous hardening.
Definition plastic.cpp:131