feat: add mitc4 material integration scaffolding

This commit is contained in:
NINI
2026-05-04 22:34:53 +09:00
parent d550e13e77
commit 2e1bd99d05
5 changed files with 433 additions and 6 deletions
+4 -4
View File
@@ -13,7 +13,7 @@ Every new agent session must read this file together with `PROGRESS.md` before p
- If an item becomes obsolete, move it to `PROGRESS.md` with a short reason instead of silently deleting it.
## Current Objective
Continue the new Phase 1 rebaseline plan in `phases/1-linear-static-mitc4-rebaseline`, starting with P1R-09 material matrix, transform, and `2 x 2 x 2` integration scaffolding. The old `phases/1-linear-static-mitc4` path is historical and superseded by the paper-based MITC4 formulation reset.
Continue the new Phase 1 rebaseline plan in `phases/1-linear-static-mitc4-rebaseline`, starting with P1R-10 MITC4 stiffness, internal force, six-DOF transform, and drilling stabilization. The old `phases/1-linear-static-mitc4` path is historical and superseded by the paper-based MITC4 formulation reset.
## Required Reading For New Agents
1. `AGENTS.md`
@@ -36,7 +36,7 @@ Continue the new Phase 1 rebaseline plan in `phases/1-linear-static-mitc4-rebase
## Active Phase Files
- Active phase directory: `phases/1-linear-static-mitc4-rebaseline`
- Execute with: `python scripts/execute.py 1-linear-static-mitc4-rebaseline`
- Step numbering is zero-based. `step0.md` is complete and recorded in `phases/1-linear-static-mitc4-rebaseline/step0-audit.md`; `step1.md` is complete and created the `quad_02_phase1.inp` normalized reference path; `step2.md` is complete and revalidated core harness guardrails; `step3.md` is complete and revalidated the Phase 1 parser/domain subset; `step4.md` is complete and strengthened validation/singular diagnostics; `step5.md` is complete and revalidated the DofManager/reaction foundation; `step6.md` is complete and revalidated the minimum result model plus displacement CSV comparator; `step7.md` is complete and revalidated MITC4 natural coordinates, tying points, center directors, and integration bases; `step8.md` is complete and revalidated degenerated-continuum displacement, direct covariant strain rows, and MITC shear tying rows; `step15.md` is the independent evaluator closeout.
- Step numbering is zero-based. `step0.md` is complete and recorded in `phases/1-linear-static-mitc4-rebaseline/step0-audit.md`; `step1.md` is complete and created the `quad_02_phase1.inp` normalized reference path; `step2.md` is complete and revalidated core harness guardrails; `step3.md` is complete and revalidated the Phase 1 parser/domain subset; `step4.md` is complete and strengthened validation/singular diagnostics; `step5.md` is complete and revalidated the DofManager/reaction foundation; `step6.md` is complete and revalidated the minimum result model plus displacement CSV comparator; `step7.md` is complete and revalidated MITC4 natural coordinates, tying points, center directors, and integration bases; `step8.md` is complete and revalidated degenerated-continuum displacement, direct covariant strain rows, and MITC shear tying rows; `step9.md` is complete and revalidated plane-stress material, convected-to-local transform, and `2 x 2 x 2` material integration scaffolding; `step15.md` is the independent evaluator closeout.
- Every step file contains a sprint contract with objective, required reading, scope, allowed files, explicit non-goals, tests to write first, reference artifacts, acceptance command, evaluator checklist, and handoff requirements.
- Historical phase directory: `phases/1-linear-static-mitc4`
- Historical phase status: blocked/superseded. Do not resume the old P1-15/P1-16 path unless the user explicitly requests recovery of that exact phase.
@@ -76,7 +76,7 @@ Each gate should be satisfied before moving to the next implementation band unle
| G1 - Build and validation | satisfied | Build system, test framework, and `scripts/validate_workspace.py` run real checks. | Validation command output |
| G2 - Parser and domain | satisfied | Parser subset revalidated in step 3; validation and singular diagnostics revalidated in step 4. | Parser acceptance/rejection tests, validation negative tests, and validation output |
| G3 - DOF/math/results infrastructure | partial | Core aliases, DOF mapping, validation harness, model diagnostic context, DofManager, sparse-connectivity inputs, full-vector reaction formula, result model metadata, and displacement CSV comparator were revalidated in steps 2, 5, and 6; assembly remains for step 12. | P1R-02, P1R-05, and P1R-06 validation output |
| G4 - MITC4 element readiness | partial | MITC4 formulation was rewritten from local papers; Steps 7 and 8 rebuilt geometry/director/local-basis scaffolding, displacement interpolation, direct covariant strain rows, and MITC shear tying rows, while material/integration, stiffness/drilling, and patch benchmarks remain for steps 9 through 11. | P1R-07 and P1R-08 validation output; future element tests |
| G4 - MITC4 element readiness | partial | MITC4 formulation was rewritten from local papers; Steps 7 through 9 rebuilt geometry/director/local-basis scaffolding, displacement interpolation, direct covariant strain rows, MITC shear tying rows, plane-stress material, convected-to-local transform, and `2 x 2 x 2` material integration scaffolding, while stiffness/drilling and patch benchmarks remain for steps 10 and 11. | P1R-07 through P1R-09 validation output; future element tests |
| G5 - End-to-end solver | reopened | Linear static path must be revalidated through steps 13 and 14 after the MITC4 rebuild and `quad_02` compatibility path. | Future integration/reference regression output |
## Phase 1 Implementation Milestones
@@ -90,7 +90,7 @@ All milestones are intended to become one or more self-contained sprint contract
| P1R-06 | completed | results generator | Rebuild minimum results model and displacement CSV comparator. | none | U/RF schema tests and CSV comparator tests |
| P1R-07 | completed | MITC4 generator | Implement MITC4 geometry, node order, tying points, directors, and local bases. | none | Shape/basis/diagnostic tests |
| P1R-08 | completed | MITC4 generator | Implement degenerated-continuum displacement, covariant strain rows, and MITC shear tying. | P1R-07 | Finite-difference and tying interpolation tests |
| P1R-09 | pending | MITC4 generator | Implement material matrix, transform, and `2 x 2 x 2` integration scaffolding. | P1R-08 | Material/integration tests |
| P1R-09 | completed | MITC4 generator | Implement material matrix, transform, and `2 x 2 x 2` integration scaffolding. | P1R-08 | Material/integration tests |
| P1R-10 | pending | MITC4 generator | Assemble MITC4 stiffness/internal force with six-DOF transform and drilling stabilization. | P1R-09, P1R-05 | Symmetry, rigid body, drilling sensitivity tests |
| P1R-11 | pending | verification generator | Add MITC4 patch, locking-sensitivity, and benchmark tests. | P1R-10 | Membrane/bending/shear/twist/locking tests |
| P1R-12 | pending | assembly generator | Rebuild assembly, solver adapter boundary, constrained solve, and full-vector RF recovery. | P1R-05, P1R-10 | Assembly and full-vector reaction tests |
+27 -1
View File
@@ -13,10 +13,36 @@ Every new agent session must read this file together with `PLAN.md` before plann
- Do not remove history unless the user explicitly asks for archival cleanup.
## Current Status
Phase 1 has a new rebaseline phase definition in `phases/1-linear-static-mitc4-rebaseline`. Steps 0 through 8 are complete. `quad_02_phase1.inp` is now the normalized Phase 1-compatible input path for the stored `quad_02` S4 reference pair, while the original `quad_02.inp` remains preserved unsupported provenance. Core numeric aliases, DOF mapping, validation harness, model diagnostic context, the Phase 1 parser/domain subset, validation/singular diagnostics, DofManager/reaction foundation, minimum result model metadata, displacement CSV comparator foundation, MITC4 geometry/director scaffolding, and MITC4 displacement/strain/tying row scaffolding have been revalidated. The old `phases/1-linear-static-mitc4` path is historical and superseded after the MITC4 formulation reset.
Phase 1 has a new rebaseline phase definition in `phases/1-linear-static-mitc4-rebaseline`. Steps 0 through 9 are complete. `quad_02_phase1.inp` is now the normalized Phase 1-compatible input path for the stored `quad_02` S4 reference pair, while the original `quad_02.inp` remains preserved unsupported provenance. Core numeric aliases, DOF mapping, validation harness, model diagnostic context, the Phase 1 parser/domain subset, validation/singular diagnostics, DofManager/reaction foundation, minimum result model metadata, displacement CSV comparator foundation, MITC4 geometry/director scaffolding, MITC4 displacement/strain/tying row scaffolding, and MITC4 material/transform/integration scaffolding have been revalidated. The old `phases/1-linear-static-mitc4` path is historical and superseded after the MITC4 formulation reset.
## Completed Work
### 2026-05-04 - P1R-09 MITC4 material integration scaffolding completed
Author: Codex
Changed files:
- `include/fesa/fesa.hpp`
- `tests/test_main.cpp`
- `phases/1-linear-static-mitc4-rebaseline/index.json`
- `PLAN.md`
- `PROGRESS.md`
Summary:
- Added tests and implementation for the documented isotropic local plane-stress shell material matrix in strain order `[eps11, eps22, eps33, gamma23, gamma13, gamma12]`, with `sigma_33 = 0` and transverse shear correction `kappa = 5/6`.
- Added material diagnostics for invalid elastic modulus, Poisson ratio, and shear correction factor.
- Added `2 x 2 x 2` Gauss integration point infrastructure with unit weights and total natural-domain weight `8`.
- Added covariant-to-local strain transformation and `D_convected = T^T D_hat T`, including identity and flat-element energy-equivalence tests.
- Added MITC4 material integration sample scaffolding that carries integration basis, tied strain rows, local material, strain transform, and convected material for the later stiffness rebuild.
Verification:
- First ran `python scripts/validate_workspace.py` after adding Step 9 tests; it failed as expected because the MITC4 material/integration APIs did not exist yet.
- After implementation, `python scripts/validate_workspace.py` configured CMake, built `fesa_core` and `fesa_tests`, and ran CTest successfully.
- CTest result: 1 test executable passed.
Follow-up:
- Continue with P1R-10 MITC4 stiffness/internal force rebuild, six-DOF local-to-global transform, and drilling stabilization.
- The legacy `MITC4ElementKernel::stiffness` path still needs to be rebuilt against the Step 7 through Step 9 formulation helpers; Step 9 intentionally did not add reduced integration, hourglass logic, stress/result output recovery, or the final drilling stiffness policy.
### 2026-05-04 - P1R-08 MITC4 covariant strain tying completed
Author: Codex
+252
View File
@@ -1304,6 +1304,7 @@ struct MITC4IntegrationBasis {
using MITC4ElementDofVector = std::array<Real, 24>;
using MITC4StrainVector = std::array<Real, 6>;
using MITC4StrainRow = std::array<Real, 24>;
using MITC4MaterialMatrix = std::array<std::array<Real, 6>, 6>;
enum class MITC4StrainComponent {
Eps11 = 0,
@@ -1359,6 +1360,54 @@ struct MITC4StrainRows {
}
};
struct MITC4MaterialMatrixEvaluation {
MITC4MaterialMatrix matrix{};
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
struct MITC4StrainTransform {
MITC4MaterialMatrix matrix{};
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
struct MITC4IntegrationPoint {
Real xi = 0.0;
Real eta = 0.0;
Real zeta = 0.0;
Real weight = 0.0;
};
struct MITC4MaterialIntegrationSample {
MITC4IntegrationPoint point;
MITC4IntegrationBasis basis;
MITC4StrainRows strain_rows;
MITC4MaterialMatrix local_material{};
MITC4MaterialMatrix strain_transform{};
MITC4MaterialMatrix convected_material{};
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
struct MITC4MaterialIntegrationData {
std::vector<MITC4MaterialIntegrationSample> samples;
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
inline Vec3 globalEX() {
return {1.0, 0.0, 0.0};
}
@@ -1624,6 +1673,209 @@ inline MITC4StrainRows mitc4TiedCovariantStrainRows(const MITC4Geometry& geometr
return result;
}
inline std::array<MITC4IntegrationPoint, 8> mitc4GaussQuadrature2x2x2() {
const Real gauss = 1.0 / std::sqrt(3.0);
const std::array<Real, 2> points = {-gauss, gauss};
std::array<MITC4IntegrationPoint, 8> integration_points{};
std::size_t index = 0;
for (Real xi : points) {
for (Real eta : points) {
for (Real zeta : points) {
integration_points[index++] = {xi, eta, zeta, 1.0};
}
}
}
return integration_points;
}
inline MITC4MaterialMatrixEvaluation mitc4PlaneStressMaterialMatrix(Real elastic_modulus, Real poisson_ratio,
Real shear_correction = 5.0 / 6.0,
Real tolerance = 1.0e-12) {
MITC4MaterialMatrixEvaluation result;
if (!isFinite(elastic_modulus) || elastic_modulus <= tolerance) {
result.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-MATERIAL", "MITC4 elastic modulus must be positive and finite"));
}
if (!isFinite(poisson_ratio) || poisson_ratio <= -1.0 || poisson_ratio >= 0.5) {
result.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-POISSON", "MITC4 isotropic Poisson ratio must satisfy -1 < nu < 0.5"));
}
if (!isFinite(shear_correction) || shear_correction <= tolerance) {
result.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-SHEAR-CORRECTION", "MITC4 shear correction factor must be positive and finite"));
}
if (hasError(result.diagnostics)) {
return result;
}
const Real scale = elastic_modulus / (1.0 - poisson_ratio * poisson_ratio);
const Real shear_modulus = elastic_modulus / (2.0 * (1.0 + poisson_ratio));
const std::size_t eps11 = strainComponentIndex(MITC4StrainComponent::Eps11);
const std::size_t eps22 = strainComponentIndex(MITC4StrainComponent::Eps22);
const std::size_t gamma23 = strainComponentIndex(MITC4StrainComponent::Gamma23);
const std::size_t gamma13 = strainComponentIndex(MITC4StrainComponent::Gamma13);
const std::size_t gamma12 = strainComponentIndex(MITC4StrainComponent::Gamma12);
result.matrix[eps11][eps11] = scale;
result.matrix[eps11][eps22] = poisson_ratio * scale;
result.matrix[eps22][eps11] = poisson_ratio * scale;
result.matrix[eps22][eps22] = scale;
result.matrix[gamma23][gamma23] = shear_correction * shear_modulus;
result.matrix[gamma13][gamma13] = shear_correction * shear_modulus;
result.matrix[gamma12][gamma12] = shear_modulus;
return result;
}
inline MITC4StrainVector multiplyMITC4MaterialMatrix(const MITC4MaterialMatrix& matrix, const MITC4StrainVector& vector) {
MITC4StrainVector result{};
for (std::size_t row = 0; row < 6; ++row) {
for (std::size_t col = 0; col < 6; ++col) {
result[row] += matrix[row][col] * vector[col];
}
}
return result;
}
inline Real dotMITC4Vector(const MITC4StrainVector& a, const MITC4StrainVector& b) {
Real result = 0.0;
for (std::size_t i = 0; i < 6; ++i) {
result += a[i] * b[i];
}
return result;
}
inline MITC4MaterialMatrix mitc4TransformMaterialMatrix(const MITC4MaterialMatrix& local_material,
const MITC4MaterialMatrix& covariant_to_local) {
MITC4MaterialMatrix result{};
for (std::size_t i = 0; i < 6; ++i) {
for (std::size_t j = 0; j < 6; ++j) {
Real value = 0.0;
for (std::size_t a = 0; a < 6; ++a) {
for (std::size_t b = 0; b < 6; ++b) {
value += covariant_to_local[a][i] * local_material[a][b] * covariant_to_local[b][j];
}
}
result[i][j] = value;
}
}
return result;
}
inline std::array<std::array<Real, 3>, 3> mitc4TensorFromEngineeringComponent(std::size_t component) {
std::array<std::array<Real, 3>, 3> tensor{};
switch (static_cast<MITC4StrainComponent>(component)) {
case MITC4StrainComponent::Eps11:
tensor[0][0] = 1.0;
break;
case MITC4StrainComponent::Eps22:
tensor[1][1] = 1.0;
break;
case MITC4StrainComponent::Eps33:
tensor[2][2] = 1.0;
break;
case MITC4StrainComponent::Gamma23:
tensor[1][2] = 0.5;
tensor[2][1] = 0.5;
break;
case MITC4StrainComponent::Gamma13:
tensor[0][2] = 0.5;
tensor[2][0] = 0.5;
break;
case MITC4StrainComponent::Gamma12:
tensor[0][1] = 0.5;
tensor[1][0] = 0.5;
break;
}
return tensor;
}
inline MITC4StrainVector mitc4EngineeringVectorFromTensor(const std::array<std::array<Real, 3>, 3>& tensor) {
MITC4StrainVector vector{};
vector[strainComponentIndex(MITC4StrainComponent::Eps11)] = tensor[0][0];
vector[strainComponentIndex(MITC4StrainComponent::Eps22)] = tensor[1][1];
vector[strainComponentIndex(MITC4StrainComponent::Eps33)] = tensor[2][2];
vector[strainComponentIndex(MITC4StrainComponent::Gamma23)] = 2.0 * tensor[1][2];
vector[strainComponentIndex(MITC4StrainComponent::Gamma13)] = 2.0 * tensor[0][2];
vector[strainComponentIndex(MITC4StrainComponent::Gamma12)] = 2.0 * tensor[0][1];
return vector;
}
inline MITC4StrainTransform mitc4CovariantToLocalStrainTransform(const MITC4IntegrationBasis& basis,
Real tolerance = 1.0e-12) {
MITC4StrainTransform result;
result.diagnostics = basis.diagnostics;
const Real jacobian = dot(cross(basis.g1, basis.g2), basis.g3);
if (!isFinite(jacobian) || std::fabs(jacobian) <= tolerance) {
result.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-SINGULAR-JACOBIAN", "MITC4 material transform Jacobian is near zero"));
return result;
}
if (hasError(result.diagnostics)) {
return result;
}
const std::array<Vec3, 3> contravariant = {
(1.0 / jacobian) * cross(basis.g2, basis.g3),
(1.0 / jacobian) * cross(basis.g3, basis.g1),
(1.0 / jacobian) * cross(basis.g1, basis.g2)};
const std::array<Vec3, 3> local = {basis.local.e1, basis.local.e2, basis.local.e3};
std::array<std::array<Real, 3>, 3> direction_cosines{};
for (std::size_t local_axis = 0; local_axis < 3; ++local_axis) {
for (std::size_t convected_axis = 0; convected_axis < 3; ++convected_axis) {
direction_cosines[local_axis][convected_axis] = dot(local[local_axis], contravariant[convected_axis]);
}
}
for (std::size_t column = 0; column < 6; ++column) {
const auto covariant_tensor = mitc4TensorFromEngineeringComponent(column);
std::array<std::array<Real, 3>, 3> local_tensor{};
for (std::size_t a = 0; a < 3; ++a) {
for (std::size_t b = 0; b < 3; ++b) {
for (std::size_t i = 0; i < 3; ++i) {
for (std::size_t j = 0; j < 3; ++j) {
local_tensor[a][b] += direction_cosines[a][i] * direction_cosines[b][j] * covariant_tensor[i][j];
}
}
}
}
const auto local_vector = mitc4EngineeringVectorFromTensor(local_tensor);
for (std::size_t row = 0; row < 6; ++row) {
result.matrix[row][column] = local_vector[row];
}
}
return result;
}
inline MITC4MaterialIntegrationData mitc4BuildMaterialIntegrationData(const MITC4Geometry& geometry, Real elastic_modulus,
Real poisson_ratio,
Real shear_correction = 5.0 / 6.0) {
MITC4MaterialIntegrationData data;
const auto material = mitc4PlaneStressMaterialMatrix(elastic_modulus, poisson_ratio, shear_correction);
appendDiagnostics(data.diagnostics, material.diagnostics);
if (hasError(data.diagnostics)) {
return data;
}
for (const MITC4IntegrationPoint& point : mitc4GaussQuadrature2x2x2()) {
MITC4MaterialIntegrationSample sample;
sample.point = point;
sample.local_material = material.matrix;
sample.basis = computeMITC4IntegrationBasis(geometry, point.xi, point.eta, point.zeta);
appendDiagnostics(sample.diagnostics, sample.basis.diagnostics);
const auto transform = mitc4CovariantToLocalStrainTransform(sample.basis);
sample.strain_transform = transform.matrix;
appendDiagnostics(sample.diagnostics, transform.diagnostics);
sample.strain_rows = mitc4TiedCovariantStrainRows(geometry, point.xi, point.eta, point.zeta);
appendDiagnostics(sample.diagnostics, sample.strain_rows.diagnostics);
if (!hasError(sample.diagnostics)) {
sample.convected_material = mitc4TransformMaterialMatrix(sample.local_material, sample.strain_transform);
}
appendDiagnostics(data.diagnostics, sample.diagnostics);
data.samples.push_back(sample);
}
return data;
}
inline LocalBasis computeLocalBasis(const std::array<Vec3, 4>& coordinates) {
const MITC4Geometry geometry = buildMITC4Geometry(coordinates, 1.0);
if (!geometry.ok()) {
@@ -11,7 +11,7 @@
{ "step": 6, "name": "results-comparator-foundation", "status": "completed" },
{ "step": 7, "name": "mitc4-geometry-directors", "status": "completed" },
{ "step": 8, "name": "mitc4-covariant-strain-tying", "status": "completed" },
{ "step": 9, "name": "mitc4-material-integration", "status": "pending" },
{ "step": 9, "name": "mitc4-material-integration", "status": "completed" },
{ "step": 10, "name": "mitc4-stiffness-drilling", "status": "pending" },
{ "step": 11, "name": "mitc4-patch-benchmark-tests", "status": "pending" },
{ "step": 12, "name": "assembly-sparse-solver-path", "status": "pending" },
+149
View File
@@ -1070,6 +1070,155 @@ FESA_TEST(mitc4_mitc_gauss_shear_rows_are_interpolated_from_tying_rows) {
}
}
FESA_TEST(mitc4_plane_stress_material_matrix_uses_documented_order_and_shear_correction) {
constexpr fesa::Real elastic_modulus = 210.0;
constexpr fesa::Real poisson_ratio = 0.3;
constexpr fesa::Real kappa = 5.0 / 6.0;
const auto law = fesa::mitc4PlaneStressMaterialMatrix(elastic_modulus, poisson_ratio, kappa);
FESA_CHECK(law.ok());
const auto eps11 = fesa::strainComponentIndex(fesa::MITC4StrainComponent::Eps11);
const auto eps22 = fesa::strainComponentIndex(fesa::MITC4StrainComponent::Eps22);
const auto eps33 = fesa::strainComponentIndex(fesa::MITC4StrainComponent::Eps33);
const auto gamma23 = fesa::strainComponentIndex(fesa::MITC4StrainComponent::Gamma23);
const auto gamma13 = fesa::strainComponentIndex(fesa::MITC4StrainComponent::Gamma13);
const auto gamma12 = fesa::strainComponentIndex(fesa::MITC4StrainComponent::Gamma12);
const fesa::Real plane_stress_scale = elastic_modulus / (1.0 - poisson_ratio * poisson_ratio);
const fesa::Real shear_modulus = elastic_modulus / (2.0 * (1.0 + poisson_ratio));
FESA_CHECK(fesa::mitc4StrainComponentLabels() ==
(std::array<std::string, 6>{"eps11", "eps22", "eps33", "gamma23", "gamma13", "gamma12"}));
FESA_CHECK_NEAR(law.matrix[eps11][eps11], plane_stress_scale, 1.0e-12);
FESA_CHECK_NEAR(law.matrix[eps11][eps22], poisson_ratio * plane_stress_scale, 1.0e-12);
FESA_CHECK_NEAR(law.matrix[eps22][eps11], poisson_ratio * plane_stress_scale, 1.0e-12);
FESA_CHECK_NEAR(law.matrix[eps22][eps22], plane_stress_scale, 1.0e-12);
for (std::size_t component = 0; component < 6; ++component) {
FESA_CHECK_NEAR(law.matrix[eps33][component], 0.0, 1.0e-15);
FESA_CHECK_NEAR(law.matrix[component][eps33], 0.0, 1.0e-15);
}
FESA_CHECK_NEAR(law.matrix[gamma23][gamma23], kappa * shear_modulus, 1.0e-12);
FESA_CHECK_NEAR(law.matrix[gamma13][gamma13], kappa * shear_modulus, 1.0e-12);
FESA_CHECK_NEAR(law.matrix[gamma12][gamma12], shear_modulus, 1.0e-12);
FESA_CHECK_NEAR(law.matrix[gamma13][gamma23], 0.0, 1.0e-15);
fesa::MITC4StrainVector strain{};
strain[gamma23] = 2.0;
strain[gamma13] = 3.0;
strain[gamma12] = 4.0;
const auto stress = fesa::multiplyMITC4MaterialMatrix(law.matrix, strain);
FESA_CHECK_NEAR(stress[gamma23], 2.0 * kappa * shear_modulus, 1.0e-12);
FESA_CHECK_NEAR(stress[gamma13], 3.0 * kappa * shear_modulus, 1.0e-12);
FESA_CHECK_NEAR(stress[gamma12], 4.0 * shear_modulus, 1.0e-12);
}
FESA_TEST(mitc4_material_matrix_reports_invalid_elastic_inputs) {
auto invalid_e = fesa::mitc4PlaneStressMaterialMatrix(-1.0, 0.3);
FESA_CHECK(!invalid_e.ok());
FESA_CHECK(fesa::containsDiagnostic(invalid_e.diagnostics, "FESA-MITC4-MATERIAL"));
auto invalid_nu = fesa::mitc4PlaneStressMaterialMatrix(1000.0, 0.5);
FESA_CHECK(!invalid_nu.ok());
FESA_CHECK(fesa::containsDiagnostic(invalid_nu.diagnostics, "FESA-MITC4-POISSON"));
auto invalid_kappa = fesa::mitc4PlaneStressMaterialMatrix(1000.0, 0.25, 0.0);
FESA_CHECK(!invalid_kappa.ok());
FESA_CHECK(fesa::containsDiagnostic(invalid_kappa.diagnostics, "FESA-MITC4-SHEAR-CORRECTION"));
}
FESA_TEST(mitc4_gauss_integration_uses_2x2x2_points_and_unit_weights) {
const auto points = fesa::mitc4GaussQuadrature2x2x2();
FESA_CHECK(points.size() == 8);
const fesa::Real gauss = 1.0 / std::sqrt(3.0);
fesa::Real total_weight = 0.0;
int positive_xi = 0;
int positive_eta = 0;
int positive_zeta = 0;
for (const auto& point : points) {
FESA_CHECK_NEAR(std::fabs(point.xi), gauss, 1.0e-15);
FESA_CHECK_NEAR(std::fabs(point.eta), gauss, 1.0e-15);
FESA_CHECK_NEAR(std::fabs(point.zeta), gauss, 1.0e-15);
FESA_CHECK_NEAR(point.weight, 1.0, 1.0e-15);
total_weight += point.weight;
positive_xi += point.xi > 0.0 ? 1 : 0;
positive_eta += point.eta > 0.0 ? 1 : 0;
positive_zeta += point.zeta > 0.0 ? 1 : 0;
}
FESA_CHECK_NEAR(total_weight, 8.0, 1.0e-15);
FESA_CHECK(positive_xi == 4);
FESA_CHECK(positive_eta == 4);
FESA_CHECK(positive_zeta == 4);
}
FESA_TEST(mitc4_covariant_to_local_transform_is_identity_for_orthonormal_basis) {
fesa::MITC4IntegrationBasis basis;
basis.g1 = {1.0, 0.0, 0.0};
basis.g2 = {0.0, 1.0, 0.0};
basis.g3 = {0.0, 0.0, 1.0};
basis.local = {{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}};
basis.jacobian = 1.0;
const auto transform = fesa::mitc4CovariantToLocalStrainTransform(basis);
FESA_CHECK(transform.ok());
for (std::size_t row = 0; row < 6; ++row) {
for (std::size_t col = 0; col < 6; ++col) {
FESA_CHECK_NEAR(transform.matrix[row][col], row == col ? 1.0 : 0.0, 1.0e-15);
}
}
}
FESA_TEST(mitc4_flat_element_material_transform_scales_convected_strains_to_local_frame) {
const std::array<fesa::Vec3, 4> coords = {{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}};
const auto geometry = fesa::buildMITC4Geometry(coords, 0.2);
FESA_CHECK(geometry.ok());
const auto basis = fesa::computeMITC4IntegrationBasis(geometry, 0.0, 0.0, 0.0);
FESA_CHECK(basis.ok());
const auto transform = fesa::mitc4CovariantToLocalStrainTransform(basis);
FESA_CHECK(transform.ok());
const std::array<fesa::Real, 6> expected_diagonal = {4.0, 4.0, 100.0, 20.0, 20.0, 4.0};
for (std::size_t row = 0; row < 6; ++row) {
for (std::size_t col = 0; col < 6; ++col) {
FESA_CHECK_NEAR(transform.matrix[row][col], row == col ? expected_diagonal[row] : 0.0, 1.0e-13);
}
}
const auto law = fesa::mitc4PlaneStressMaterialMatrix(1000.0, 0.25);
FESA_CHECK(law.ok());
const auto convected = fesa::mitc4TransformMaterialMatrix(law.matrix, transform.matrix);
fesa::MITC4StrainVector local_strain{};
local_strain[fesa::strainComponentIndex(fesa::MITC4StrainComponent::Eps11)] = 0.02;
local_strain[fesa::strainComponentIndex(fesa::MITC4StrainComponent::Eps22)] = -0.01;
local_strain[fesa::strainComponentIndex(fesa::MITC4StrainComponent::Gamma12)] = 0.03;
fesa::MITC4StrainVector convected_strain{};
for (std::size_t component = 0; component < 6; ++component) {
convected_strain[component] = local_strain[component] / expected_diagonal[component];
}
const auto local_stress = fesa::multiplyMITC4MaterialMatrix(law.matrix, local_strain);
const auto convected_stress = fesa::multiplyMITC4MaterialMatrix(convected, convected_strain);
const fesa::Real local_energy = fesa::dotMITC4Vector(local_strain, local_stress);
const fesa::Real convected_energy = fesa::dotMITC4Vector(convected_strain, convected_stress);
FESA_CHECK_NEAR(convected_energy, local_energy, 1.0e-12);
}
FESA_TEST(mitc4_material_integration_samples_carry_tied_rows_basis_and_transformed_material) {
const std::array<fesa::Vec3, 4> coords = {{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}};
const auto geometry = fesa::buildMITC4Geometry(coords, 0.2);
FESA_CHECK(geometry.ok());
const auto data = fesa::mitc4BuildMaterialIntegrationData(geometry, 1000.0, 0.3);
FESA_CHECK(data.ok());
FESA_CHECK(data.samples.size() == 8);
for (const auto& sample : data.samples) {
FESA_CHECK(sample.ok());
FESA_CHECK_NEAR(sample.point.weight, 1.0, 1.0e-15);
FESA_CHECK(sample.basis.ok());
FESA_CHECK(sample.strain_rows.ok());
for (std::size_t i = 0; i < 6; ++i) {
for (std::size_t j = 0; j < 6; ++j) {
FESA_CHECK_NEAR(sample.convected_material[i][j], sample.convected_material[j][i], 1.0e-10);
}
}
}
}
FESA_TEST(mitc4_shape_functions_and_stiffness_baseline) {
auto shape = fesa::shapeFunctions(0.25, -0.5);
const fesa::Real sum = shape.n[0] + shape.n[1] + shape.n[2] + shape.n[3];