Files
FESADev/include/fesa/fesa.hpp
T

1177 lines
44 KiB
C++

#pragma once
#include "fesa/Boundary/Boundary.hpp"
#include "fesa/Core/Core.hpp"
#include "fesa/IO/IO.hpp"
#include "fesa/Load/Load.hpp"
#include "fesa/Math/Math.hpp"
#include "fesa/ModuleInfo.hpp"
#include "fesa/Property/Property.hpp"
#include "fesa/Results/Results.hpp"
#include "fesa/Util/Util.hpp"
#include <algorithm>
#include <array>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <fstream>
#include <initializer_list>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
namespace fesa {
inline SparsePattern buildReducedSparsePattern(const Domain& domain, const DofManager& dofs) {
SparsePattern pattern;
pattern.equation_count = dofs.freeDofCount();
std::set<std::pair<EquationId, EquationId>> ordered_entries;
for (const auto& [element_id, element] : domain.elements) {
(void)element_id;
const auto equations = dofs.elementEquationIds(element);
for (EquationId row : equations) {
if (row < 0) {
continue;
}
for (EquationId col : equations) {
if (col < 0) {
continue;
}
ordered_entries.insert({row, col});
}
}
}
pattern.entries.reserve(ordered_entries.size());
for (const auto& entry : ordered_entries) {
pattern.entries.push_back({entry.first, entry.second});
}
return pattern;
}
inline std::vector<Real> recoverFullReaction(const DenseMatrix& k_full, const std::vector<Real>& u_full, const std::vector<Real>& f_full) {
if (k_full.rows() != k_full.cols() || static_cast<LocalIndex>(u_full.size()) != k_full.cols() ||
static_cast<LocalIndex>(f_full.size()) != k_full.rows()) {
throw std::runtime_error("full reaction size mismatch");
}
std::vector<Real> reaction = k_full.multiply(u_full);
for (std::size_t i = 0; i < reaction.size(); ++i) {
reaction[i] -= f_full[i];
}
return reaction;
}
struct ShapeData {
std::array<Real, 4> n{};
std::array<Real, 4> dr{};
std::array<Real, 4> ds{};
};
inline ShapeData shapeFunctions(Real r, Real s) {
return {{
0.25 * (1.0 - r) * (1.0 - s),
0.25 * (1.0 + r) * (1.0 - s),
0.25 * (1.0 + r) * (1.0 + s),
0.25 * (1.0 - r) * (1.0 + s),
},
{
-0.25 * (1.0 - s),
0.25 * (1.0 - s),
0.25 * (1.0 + s),
-0.25 * (1.0 + s),
},
{
-0.25 * (1.0 - r),
-0.25 * (1.0 + r),
0.25 * (1.0 + r),
0.25 * (1.0 - r),
}};
}
struct LocalBasis {
Vec3 e1;
Vec3 e2;
Vec3 e3;
};
struct MITC4NaturalPoint {
Real xi = 0.0;
Real eta = 0.0;
};
struct MITC4TyingPoint {
std::string label;
MITC4NaturalPoint natural;
std::array<LocalIndex, 2> edge_node_indices{};
};
inline std::array<MITC4NaturalPoint, 4> mitc4NodeNaturalCoordinates() {
return {{{-1.0, -1.0}, {1.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0}}};
}
inline std::array<MITC4TyingPoint, 4> mitc4TyingPoints() {
return {{{"A", {0.0, -1.0}, {0, 1}},
{"B", {-1.0, 0.0}, {0, 3}},
{"C", {0.0, 1.0}, {3, 2}},
{"D", {1.0, 0.0}, {1, 2}}}};
}
struct MITC4DirectorFrame {
Vec3 v1;
Vec3 v2;
Vec3 vn;
};
struct MITC4MidsurfaceDerivatives {
ShapeData shape;
Vec3 g1;
Vec3 g2;
};
struct MITC4Geometry {
std::array<Vec3, 4> coordinates{};
Real thickness = 0.0;
ShapeData center_shape;
Vec3 g1_center;
Vec3 g2_center;
Vec3 center_normal;
std::array<MITC4DirectorFrame, 4> nodal_frames{};
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
struct MITC4IntegrationBasis {
ShapeData shape;
Vec3 g1;
Vec3 g2;
Vec3 g3;
Real jacobian = 0.0;
LocalBasis local;
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
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,
Eps22 = 1,
Eps33 = 2,
Gamma23 = 3,
Gamma13 = 4,
Gamma12 = 5
};
inline std::size_t strainComponentIndex(MITC4StrainComponent component) {
return static_cast<std::size_t>(component);
}
inline std::array<std::string, 6> mitc4StrainComponentLabels() {
return {"eps11", "eps22", "eps33", "gamma23", "gamma13", "gamma12"};
}
struct MITC4LocalRotations {
Real alpha = 0.0;
Real beta = 0.0;
Real gamma = 0.0;
};
struct MITC4DisplacementDerivatives {
ShapeData shape;
Vec3 displacement;
Vec3 du_dxi;
Vec3 du_deta;
Vec3 du_dzeta;
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
struct MITC4StrainEvaluation {
MITC4StrainVector values{};
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
struct MITC4StrainRows {
std::array<MITC4StrainRow, 6> rows{};
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
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};
}
inline Vec3 globalEY() {
return {0.0, 1.0, 0.0};
}
inline Vec3 globalEZ() {
return {0.0, 0.0, 1.0};
}
inline Diagnostic mitc4Diagnostic(std::string code, std::string message) {
return makeDiagnostic(Severity::Error, std::move(code), std::move(message), "mitc4", "<element>", 0);
}
inline void appendDiagnostics(std::vector<Diagnostic>& target, const std::vector<Diagnostic>& source) {
target.insert(target.end(), source.begin(), source.end());
}
inline MITC4MidsurfaceDerivatives mitc4MidsurfaceDerivatives(const std::array<Vec3, 4>& coordinates, Real xi, Real eta) {
MITC4MidsurfaceDerivatives result;
result.shape = shapeFunctions(xi, eta);
for (std::size_t i = 0; i < 4; ++i) {
result.g1 = result.g1 + result.shape.dr[i] * coordinates[i];
result.g2 = result.g2 + result.shape.ds[i] * coordinates[i];
}
return result;
}
inline std::optional<Vec3> firstNormalizedCross(const std::array<Vec3, 3>& axes, const Vec3& vector, Real tolerance) {
for (const Vec3& axis : axes) {
auto candidate = normalizedIfValid(cross(axis, vector), tolerance);
if (candidate) {
return candidate;
}
}
return std::nullopt;
}
inline std::optional<MITC4DirectorFrame> buildMITC4DirectorFrame(const Vec3& normal, Real tolerance) {
auto v1 = normalizedIfValid(cross(globalEY(), normal), tolerance);
if (!v1) {
v1 = firstNormalizedCross({globalEZ(), globalEX(), globalEY()}, normal, tolerance);
}
if (!v1) {
return std::nullopt;
}
auto v2 = normalizedIfValid(cross(normal, *v1), tolerance);
if (!v2) {
return std::nullopt;
}
return MITC4DirectorFrame{*v1, *v2, normal};
}
inline MITC4Geometry buildMITC4Geometry(const std::array<Vec3, 4>& coordinates, Real thickness, Real tolerance = 1.0e-12) {
MITC4Geometry geometry;
geometry.coordinates = coordinates;
geometry.thickness = thickness;
if (!isFinite(thickness) || thickness <= tolerance) {
geometry.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-THICKNESS", "MITC4 shell thickness must be positive and finite"));
}
for (const Vec3& coordinate : coordinates) {
if (!isFinite(coordinate)) {
geometry.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-COORDINATE", "MITC4 element coordinates must be finite"));
break;
}
}
const auto center = mitc4MidsurfaceDerivatives(coordinates, 0.0, 0.0);
geometry.center_shape = center.shape;
geometry.g1_center = center.g1;
geometry.g2_center = center.g2;
const auto normal = normalizedIfValid(cross(center.g1, center.g2), tolerance);
if (!normal) {
geometry.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-SINGULAR-NORMAL", "MITC4 element center normal is near zero"));
return geometry;
}
geometry.center_normal = *normal;
const auto frame = buildMITC4DirectorFrame(*normal, tolerance);
if (!frame) {
geometry.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-SINGULAR-BASIS", "MITC4 nodal director basis could not be constructed"));
return geometry;
}
geometry.nodal_frames.fill(*frame);
return geometry;
}
inline MITC4IntegrationBasis computeMITC4IntegrationBasis(const MITC4Geometry& geometry, Real xi, Real eta, Real zeta,
Real tolerance = 1.0e-12) {
MITC4IntegrationBasis result;
result.diagnostics = geometry.diagnostics;
result.shape = shapeFunctions(xi, eta);
for (std::size_t i = 0; i < 4; ++i) {
const Vec3& coordinate = geometry.coordinates[i];
const Vec3& normal = geometry.nodal_frames[i].vn;
result.g1 = result.g1 + result.shape.dr[i] * coordinate +
(0.5 * zeta * geometry.thickness * result.shape.dr[i]) * normal;
result.g2 = result.g2 + result.shape.ds[i] * coordinate +
(0.5 * zeta * geometry.thickness * result.shape.ds[i]) * normal;
result.g3 = result.g3 + (0.5 * geometry.thickness * result.shape.n[i]) * normal;
}
result.jacobian = dot(cross(result.g1, result.g2), result.g3);
if (!isFinite(result.jacobian) || std::fabs(result.jacobian) <= tolerance) {
result.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-SINGULAR-JACOBIAN", "MITC4 element Jacobian is near zero"));
}
const auto e3 = normalizedIfValid(result.g3, tolerance);
if (!e3) {
result.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-SINGULAR-BASIS", "MITC4 integration basis normal is near zero"));
return result;
}
auto e1 = normalizedIfValid(cross(result.g2, *e3), tolerance);
if (!e1) {
e1 = firstNormalizedCross({globalEY(), globalEZ(), globalEX()}, *e3, tolerance);
}
if (!e1) {
result.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-SINGULAR-BASIS", "MITC4 integration basis tangent could not be constructed"));
return result;
}
const auto e2 = normalizedIfValid(cross(*e3, *e1), tolerance);
if (!e2) {
result.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-SINGULAR-BASIS", "MITC4 integration basis is not right-handed"));
return result;
}
result.local = {*e1, *e2, *e3};
return result;
}
inline Vec3 mitc4NodalTranslation(const MITC4ElementDofVector& values, std::size_t node) {
const std::size_t base = 6 * node;
return {values[base + 0], values[base + 1], values[base + 2]};
}
inline Vec3 mitc4NodalRotation(const MITC4ElementDofVector& values, std::size_t node) {
const std::size_t base = 6 * node;
return {values[base + 3], values[base + 4], values[base + 5]};
}
inline MITC4LocalRotations mitc4LocalRotations(const MITC4DirectorFrame& frame, const Vec3& global_rotation) {
return {dot(global_rotation, frame.v1), dot(global_rotation, frame.v2), dot(global_rotation, frame.vn)};
}
inline Vec3 mitc4DirectorIncrement(const MITC4DirectorFrame& frame, const Vec3& global_rotation) {
const MITC4LocalRotations rotations = mitc4LocalRotations(frame, global_rotation);
return (-rotations.alpha) * frame.v2 + rotations.beta * frame.v1;
}
inline MITC4DisplacementDerivatives mitc4DisplacementDerivatives(const MITC4Geometry& geometry, const MITC4ElementDofVector& values,
Real xi, Real eta, Real zeta) {
MITC4DisplacementDerivatives result;
result.diagnostics = geometry.diagnostics;
result.shape = shapeFunctions(xi, eta);
for (std::size_t node = 0; node < 4; ++node) {
const Vec3 translation = mitc4NodalTranslation(values, node);
const Vec3 rotation = mitc4NodalRotation(values, node);
const Vec3 q = mitc4DirectorIncrement(geometry.nodal_frames[node], rotation);
const Real n = result.shape.n[node];
const Real dn_dxi = result.shape.dr[node];
const Real dn_deta = result.shape.ds[node];
result.displacement = result.displacement + n * translation + (0.5 * zeta * geometry.thickness * n) * q;
result.du_dxi = result.du_dxi + dn_dxi * translation + (0.5 * zeta * geometry.thickness * dn_dxi) * q;
result.du_deta = result.du_deta + dn_deta * translation + (0.5 * zeta * geometry.thickness * dn_deta) * q;
result.du_dzeta = result.du_dzeta + (0.5 * geometry.thickness * n) * q;
}
return result;
}
inline void assignMITC4CovariantStrain(MITC4StrainVector& values, const MITC4IntegrationBasis& basis,
const MITC4DisplacementDerivatives& derivatives) {
values[strainComponentIndex(MITC4StrainComponent::Eps11)] = dot(derivatives.du_dxi, basis.g1);
values[strainComponentIndex(MITC4StrainComponent::Eps22)] = dot(derivatives.du_deta, basis.g2);
values[strainComponentIndex(MITC4StrainComponent::Eps33)] = 0.0;
values[strainComponentIndex(MITC4StrainComponent::Gamma23)] = dot(derivatives.du_deta, basis.g3) + dot(derivatives.du_dzeta, basis.g2);
values[strainComponentIndex(MITC4StrainComponent::Gamma13)] = dot(derivatives.du_dxi, basis.g3) + dot(derivatives.du_dzeta, basis.g1);
values[strainComponentIndex(MITC4StrainComponent::Gamma12)] = dot(derivatives.du_dxi, basis.g2) + dot(derivatives.du_deta, basis.g1);
}
inline MITC4StrainEvaluation mitc4DirectCovariantStrain(const MITC4Geometry& geometry, const MITC4ElementDofVector& values,
Real xi, Real eta, Real zeta) {
MITC4StrainEvaluation result;
const auto basis = computeMITC4IntegrationBasis(geometry, xi, eta, zeta);
appendDiagnostics(result.diagnostics, basis.diagnostics);
const auto derivatives = mitc4DisplacementDerivatives(geometry, values, xi, eta, zeta);
appendDiagnostics(result.diagnostics, derivatives.diagnostics);
if (hasError(result.diagnostics)) {
return result;
}
assignMITC4CovariantStrain(result.values, basis, derivatives);
return result;
}
inline MITC4StrainRows mitc4DirectCovariantStrainRows(const MITC4Geometry& geometry, Real xi, Real eta, Real zeta) {
MITC4StrainRows result;
const auto basis = computeMITC4IntegrationBasis(geometry, xi, eta, zeta);
appendDiagnostics(result.diagnostics, basis.diagnostics);
if (hasError(result.diagnostics)) {
return result;
}
for (std::size_t dof = 0; dof < 24; ++dof) {
MITC4ElementDofVector unit{};
unit.fill(0.0);
unit[dof] = 1.0;
const auto derivatives = mitc4DisplacementDerivatives(geometry, unit, xi, eta, zeta);
appendDiagnostics(result.diagnostics, derivatives.diagnostics);
if (hasError(result.diagnostics)) {
return result;
}
MITC4StrainVector values{};
assignMITC4CovariantStrain(values, basis, derivatives);
for (std::size_t component = 0; component < 6; ++component) {
result.rows[component][dof] = values[component];
}
}
return result;
}
inline MITC4StrainEvaluation evaluateMITC4StrainRows(const MITC4StrainRows& rows, const MITC4ElementDofVector& values) {
MITC4StrainEvaluation result;
result.diagnostics = rows.diagnostics;
if (hasError(result.diagnostics)) {
return result;
}
for (std::size_t component = 0; component < 6; ++component) {
for (std::size_t dof = 0; dof < 24; ++dof) {
result.values[component] += rows.rows[component][dof] * values[dof];
}
}
return result;
}
inline MITC4StrainRows mitc4TiedCovariantStrainRows(const MITC4Geometry& geometry, Real xi, Real eta, Real zeta) {
MITC4StrainRows result = mitc4DirectCovariantStrainRows(geometry, xi, eta, zeta);
const auto direct_a = mitc4DirectCovariantStrainRows(geometry, 0.0, -1.0, zeta);
const auto direct_b = mitc4DirectCovariantStrainRows(geometry, -1.0, 0.0, zeta);
const auto direct_c = mitc4DirectCovariantStrainRows(geometry, 0.0, 1.0, zeta);
const auto direct_d = mitc4DirectCovariantStrainRows(geometry, 1.0, 0.0, zeta);
appendDiagnostics(result.diagnostics, direct_a.diagnostics);
appendDiagnostics(result.diagnostics, direct_b.diagnostics);
appendDiagnostics(result.diagnostics, direct_c.diagnostics);
appendDiagnostics(result.diagnostics, direct_d.diagnostics);
if (hasError(result.diagnostics)) {
return result;
}
const std::size_t gamma23 = strainComponentIndex(MITC4StrainComponent::Gamma23);
const std::size_t gamma13 = strainComponentIndex(MITC4StrainComponent::Gamma13);
for (std::size_t dof = 0; dof < 24; ++dof) {
result.rows[gamma13][dof] = 0.5 * (1.0 - eta) * direct_a.rows[gamma13][dof] +
0.5 * (1.0 + eta) * direct_c.rows[gamma13][dof];
result.rows[gamma23][dof] = 0.5 * (1.0 - xi) * direct_b.rows[gamma23][dof] +
0.5 * (1.0 + xi) * direct_d.rows[gamma23][dof];
}
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()) {
throw std::runtime_error("invalid MITC4 geometry");
}
const MITC4DirectorFrame& frame = geometry.nodal_frames[0];
return {frame.v1, frame.v2, frame.vn};
}
struct ElementStiffnessOptions {
Real drilling_stiffness_scale = 1.0e-3;
};
struct MITC4DrillingStabilizationResult {
DenseMatrix local_with_drilling;
Real reference_diagonal = 0.0;
Real drilling_stiffness = 0.0;
Real drilling_stiffness_scale = 0.0;
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
struct MITC4ElementStiffnessResult {
DenseMatrix local_without_drilling;
DenseMatrix local_with_drilling;
DenseMatrix global;
std::size_t integration_point_count = 0;
Real drilling_reference_diagonal = 0.0;
Real drilling_stiffness = 0.0;
Real drilling_stiffness_scale = 0.0;
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
inline DenseMatrix mitc4LocalDofTransform(const MITC4Geometry& geometry) {
DenseMatrix transform(24, 24);
for (LocalIndex node = 0; node < 4; ++node) {
const LocalIndex base = 6 * node;
const auto& frame = geometry.nodal_frames[static_cast<std::size_t>(node)];
const std::array<Vec3, 3> axes = {frame.v1, frame.v2, frame.vn};
for (LocalIndex local_axis = 0; local_axis < 3; ++local_axis) {
const Vec3 axis = axes[static_cast<std::size_t>(local_axis)];
transform(base + local_axis, base + 0) = axis.x;
transform(base + local_axis, base + 1) = axis.y;
transform(base + local_axis, base + 2) = axis.z;
transform(base + 3 + local_axis, base + 3) = axis.x;
transform(base + 3 + local_axis, base + 4) = axis.y;
transform(base + 3 + local_axis, base + 5) = axis.z;
}
}
return transform;
}
inline MITC4StrainRows mitc4TransformStrainRowsToLocalDofs(const MITC4StrainRows& global_rows,
const DenseMatrix& local_dof_transform) {
MITC4StrainRows local_rows;
local_rows.diagnostics = global_rows.diagnostics;
if (hasError(local_rows.diagnostics)) {
return local_rows;
}
for (std::size_t component = 0; component < 6; ++component) {
for (LocalIndex local_dof = 0; local_dof < 24; ++local_dof) {
Real value = 0.0;
for (LocalIndex global_dof = 0; global_dof < 24; ++global_dof) {
value += global_rows.rows[component][static_cast<std::size_t>(global_dof)] *
local_dof_transform(local_dof, global_dof);
}
local_rows.rows[component][static_cast<std::size_t>(local_dof)] = value;
}
}
return local_rows;
}
inline void accumulateMITC4BtDB(DenseMatrix& stiffness, const MITC4StrainRows& rows,
const MITC4MaterialMatrix& material, Real factor) {
for (LocalIndex i = 0; i < 24; ++i) {
for (LocalIndex j = 0; j < 24; ++j) {
Real value = 0.0;
for (std::size_t a = 0; a < 6; ++a) {
for (std::size_t b = 0; b < 6; ++b) {
value += rows.rows[a][static_cast<std::size_t>(i)] * material[a][b] *
rows.rows[b][static_cast<std::size_t>(j)];
}
}
stiffness.add(i, j, value * factor);
}
}
}
inline Real mitc4MinimumPositivePhysicalLocalDiagonal(const DenseMatrix& local_without_drilling,
Real tolerance = 1.0e-12) {
Real minimum = std::numeric_limits<Real>::infinity();
for (LocalIndex node = 0; node < 4; ++node) {
for (LocalIndex local_dof = 0; local_dof < 5; ++local_dof) {
const Real diagonal = local_without_drilling(6 * node + local_dof, 6 * node + local_dof);
if (isFinite(diagonal) && diagonal > tolerance) {
minimum = std::min(minimum, diagonal);
}
}
}
return minimum;
}
inline MITC4DrillingStabilizationResult mitc4ApplyDrillingStabilization(const DenseMatrix& local_without_drilling,
Real drilling_stiffness_scale,
Real tolerance = 1.0e-12) {
MITC4DrillingStabilizationResult result;
result.local_with_drilling = local_without_drilling;
result.drilling_stiffness_scale = drilling_stiffness_scale;
if (!isFinite(drilling_stiffness_scale) || drilling_stiffness_scale < 0.0) {
result.diagnostics.push_back(
mitc4Diagnostic("FESA-MITC4-DRILLING-SCALE", "MITC4 drilling stiffness scale must be non-negative and finite"));
return result;
}
result.reference_diagonal = mitc4MinimumPositivePhysicalLocalDiagonal(local_without_drilling, tolerance);
if (!isFinite(result.reference_diagonal)) {
result.diagnostics.push_back(mitc4Diagnostic("FESA-MITC4-DRILLING-REFERENCE",
"MITC4 drilling stiffness reference diagonal is not positive"));
return result;
}
result.drilling_stiffness = drilling_stiffness_scale * result.reference_diagonal;
for (LocalIndex node = 0; node < 4; ++node) {
const LocalIndex gamma = 6 * node + 5;
result.local_with_drilling.add(gamma, gamma, result.drilling_stiffness);
}
return result;
}
inline DenseMatrix mitc4TransformLocalStiffnessToGlobal(const DenseMatrix& local_stiffness,
const DenseMatrix& local_dof_transform) {
DenseMatrix global(24, 24);
for (LocalIndex i = 0; i < 24; ++i) {
for (LocalIndex j = 0; j < 24; ++j) {
Real value = 0.0;
for (LocalIndex a = 0; a < 24; ++a) {
for (LocalIndex b = 0; b < 24; ++b) {
value += local_dof_transform(a, i) * local_stiffness(a, b) * local_dof_transform(b, j);
}
}
global(i, j) = value;
}
}
return global;
}
inline MITC4ElementStiffnessResult mitc4ElementStiffness(const std::array<Vec3, 4>& coordinates,
Real elastic_modulus, Real poisson_ratio, Real thickness,
ElementStiffnessOptions options = {}) {
MITC4ElementStiffnessResult result;
result.local_without_drilling = DenseMatrix(24, 24);
result.local_with_drilling = DenseMatrix(24, 24);
result.global = DenseMatrix(24, 24);
result.drilling_stiffness_scale = options.drilling_stiffness_scale;
const MITC4Geometry geometry = buildMITC4Geometry(coordinates, thickness);
appendDiagnostics(result.diagnostics, geometry.diagnostics);
if (hasError(result.diagnostics)) {
return result;
}
const MITC4MaterialIntegrationData integration =
mitc4BuildMaterialIntegrationData(geometry, elastic_modulus, poisson_ratio);
appendDiagnostics(result.diagnostics, integration.diagnostics);
if (hasError(result.diagnostics)) {
return result;
}
result.integration_point_count = integration.samples.size();
const DenseMatrix local_dof_transform = mitc4LocalDofTransform(geometry);
for (const MITC4MaterialIntegrationSample& sample : integration.samples) {
const MITC4StrainRows local_rows =
mitc4TransformStrainRowsToLocalDofs(sample.strain_rows, local_dof_transform);
appendDiagnostics(result.diagnostics, local_rows.diagnostics);
if (hasError(result.diagnostics)) {
return result;
}
const Real factor = std::fabs(sample.basis.jacobian) * sample.point.weight;
accumulateMITC4BtDB(result.local_without_drilling, local_rows, sample.convected_material, factor);
}
const auto drilling = mitc4ApplyDrillingStabilization(result.local_without_drilling, options.drilling_stiffness_scale);
appendDiagnostics(result.diagnostics, drilling.diagnostics);
result.local_with_drilling = drilling.local_with_drilling;
result.drilling_reference_diagonal = drilling.reference_diagonal;
result.drilling_stiffness = drilling.drilling_stiffness;
result.drilling_stiffness_scale = drilling.drilling_stiffness_scale;
if (hasError(result.diagnostics)) {
return result;
}
result.global = mitc4TransformLocalStiffnessToGlobal(result.local_with_drilling, local_dof_transform);
return result;
}
inline std::vector<Real> mitc4ElementInternalForce(const MITC4ElementStiffnessResult& stiffness,
const std::vector<Real>& element_displacement) {
if (stiffness.global.rows() != static_cast<LocalIndex>(element_displacement.size())) {
throw std::runtime_error("MITC4 internal force size mismatch");
}
return stiffness.global.multiply(element_displacement);
}
class MITC4ElementKernel {
public:
DenseMatrix stiffness(const std::array<Vec3, 4>& coordinates, Real elastic_modulus, Real poisson_ratio, Real thickness,
ElementStiffnessOptions options = {}) const {
const auto result = mitc4ElementStiffness(coordinates, elastic_modulus, poisson_ratio, thickness, options);
if (!result.ok()) {
throw std::runtime_error(result.diagnostics.empty() ? "invalid MITC4 stiffness"
: result.diagnostics.front().message);
}
return result.global;
}
};
struct AssemblyResult {
DenseMatrix k_full;
std::vector<Real> f_full;
SparsePattern reduced_pattern;
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
struct ReducedSystem {
DenseMatrix k;
std::vector<Real> f;
std::vector<LocalIndex> free_full_indices;
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
inline AssemblyResult assembleSystem(const Domain& domain, const DofManager& dofs, ElementStiffnessOptions options = {}) {
AssemblyResult result;
result.k_full = DenseMatrix(dofs.fullDofCount(), dofs.fullDofCount());
result.f_full = std::vector<Real>(static_cast<std::size_t>(dofs.fullDofCount()), 0.0);
result.reduced_pattern = buildReducedSparsePattern(domain, dofs);
if (dofs.freeDofCount() > 0 && result.reduced_pattern.nonzeroCount() == 0) {
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-SPARSE-PATTERN",
"Reduced sparse pattern has no stiffness entries", "assembly"));
}
for (const auto& [element_id, element] : domain.elements) {
const ShellSection* section = shellSectionForElement(domain, element_id);
if (section == nullptr) {
result.diagnostics.push_back({Severity::Error, "FESA-ASSEMBLY-MISSING-PROPERTY", "Element has no shell section", {}});
continue;
}
const auto material_it = domain.materials.find(Domain::key(section->material));
if (material_it == domain.materials.end()) {
result.diagnostics.push_back({Severity::Error, "FESA-ASSEMBLY-MISSING-MATERIAL", "Element material is missing", {}});
continue;
}
std::array<Vec3, 4> coordinates{};
for (std::size_t i = 0; i < 4; ++i) {
coordinates[i] = domain.nodes.at(element.node_ids[i]).coordinates;
}
const auto stiffness = mitc4ElementStiffness(coordinates, material_it->second.elastic_modulus,
material_it->second.poisson_ratio, section->thickness, options);
result.diagnostics.insert(result.diagnostics.end(), stiffness.diagnostics.begin(), stiffness.diagnostics.end());
if (!stiffness.ok()) {
continue;
}
const auto element_full_indices = dofs.elementFullDofIndices(element);
for (LocalIndex a = 0; a < 24; ++a) {
const LocalIndex ia = element_full_indices[static_cast<std::size_t>(a)];
for (LocalIndex b = 0; b < 24; ++b) {
const LocalIndex ib = element_full_indices[static_cast<std::size_t>(b)];
result.k_full.add(ia, ib, stiffness.global(a, b));
}
}
}
for (const NodalLoad& load : domain.loads) {
const auto dof = dofFromAbaqus(load.dof);
if (!dof) {
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-ASSEMBLY-LOAD-DOF",
"Nodal load references an invalid DOF", "cload"));
continue;
}
for (GlobalId node_id : resolveNodeTarget(domain, load.target, &result.diagnostics)) {
result.f_full[static_cast<std::size_t>(dofs.fullIndex(node_id, *dof))] += load.magnitude;
}
}
return result;
}
inline ReducedSystem projectToReducedSystem(const AssemblyResult& assembly, const DofManager& dofs) {
ReducedSystem result;
result.k = DenseMatrix(dofs.freeDofCount(), dofs.freeDofCount());
result.f = std::vector<Real>(static_cast<std::size_t>(dofs.freeDofCount()), 0.0);
result.free_full_indices = dofs.freeFullIndices();
if (assembly.k_full.rows() != assembly.k_full.cols() ||
assembly.k_full.rows() != dofs.fullDofCount() ||
static_cast<LocalIndex>(assembly.f_full.size()) != dofs.fullDofCount()) {
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-ASSEMBLY-SIZE",
"Full-space stiffness/load sizes do not match DofManager", "assembly"));
return result;
}
if (dofs.freeDofCount() == 0) {
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-NO-FREE-DOFS",
"No free DOFs exist after applying constraints", "dof"));
return result;
}
if (assembly.reduced_pattern.equation_count != dofs.freeDofCount() || assembly.reduced_pattern.nonzeroCount() == 0) {
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-SPARSE-PATTERN",
"Reduced sparse pattern is empty or inconsistent with free DOFs", "assembly"));
return result;
}
for (LocalIndex i = 0; i < dofs.freeDofCount(); ++i) {
const LocalIndex full_i = dofs.freeFullIndices()[static_cast<std::size_t>(i)];
result.f[static_cast<std::size_t>(i)] = assembly.f_full[static_cast<std::size_t>(full_i)];
for (LocalIndex j = 0; j < dofs.freeDofCount(); ++j) {
const LocalIndex full_j = dofs.freeFullIndices()[static_cast<std::size_t>(j)];
result.k(i, j) = assembly.k_full(full_i, full_j);
}
}
return result;
}
struct AnalysisResult {
AnalysisModel model;
AnalysisState state;
ResultFile result_file;
std::vector<Diagnostic> diagnostics;
bool ok() const {
return !hasError(diagnostics);
}
};
class Analysis {
public:
virtual ~Analysis() = default;
AnalysisResult run(const Domain& domain) const {
AnalysisResult result;
initialize(domain, result);
if (hasError(result.diagnostics)) {
return result;
}
solve(domain, result);
return result;
}
protected:
virtual void initialize(const Domain& domain, AnalysisResult& result) const {
auto diagnostics = validateDomain(domain);
result.diagnostics.insert(result.diagnostics.end(), diagnostics.begin(), diagnostics.end());
}
virtual void solve(const Domain& domain, AnalysisResult& result) const = 0;
};
class LinearStaticAnalysis final : public Analysis {
public:
explicit LinearStaticAnalysis(const LinearSolver* solver = nullptr) : solver_(solver) {}
protected:
void solve(const Domain& domain, AnalysisResult& result) const override {
result.model = buildLinearStaticAnalysisModel(domain);
result.diagnostics.insert(result.diagnostics.end(), result.model.diagnostics.begin(), result.model.diagnostics.end());
if (hasError(result.diagnostics)) {
return;
}
DofManager dofs(domain);
if (dofs.freeDofCount() == 0) {
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-NO-FREE-DOFS",
"No free DOFs exist after applying constraints", "dof"));
return;
}
AssemblyResult assembly = assembleSystem(domain, dofs);
result.diagnostics.insert(result.diagnostics.end(), assembly.diagnostics.begin(), assembly.diagnostics.end());
if (hasError(result.diagnostics)) {
return;
}
const auto reduced = projectToReducedSystem(assembly, dofs);
result.diagnostics.insert(result.diagnostics.end(), reduced.diagnostics.begin(), reduced.diagnostics.end());
if (hasError(result.diagnostics)) {
return;
}
const LinearSolver& active_solver = solver_ == nullptr ? defaultSolver() : *solver_;
SolveResult solved = active_solver.solve(reduced.k, reduced.f);
result.diagnostics.insert(result.diagnostics.end(), solved.diagnostics.begin(), solved.diagnostics.end());
if (!solved.ok()) {
return;
}
if (static_cast<LocalIndex>(solved.x.size()) != dofs.freeDofCount()) {
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SOLVER-SIZE",
"Linear solver returned a vector with the wrong size", "solver"));
return;
}
result.state.u_full = dofs.reconstructFullVector(solved.x);
result.state.f_external_full = assembly.f_full;
result.state.f_internal_full = assembly.k_full.multiply(result.state.u_full);
result.state.reaction_full = recoverFullReaction(assembly.k_full, result.state.u_full, result.state.f_external_full);
result.state.converged = true;
InMemoryResultsWriter writer;
writer.writeLinearStatic(domain, result.model, dofs, result.state.u_full, result.state.reaction_full);
result.result_file = writer.result();
}
private:
static const LinearSolver& defaultSolver() {
static const GaussianEliminationSolver solver;
return solver;
}
const LinearSolver* solver_ = nullptr;
};
inline AnalysisResult runLinearStaticInputString(const std::string& text,
const std::string& source_name = "<memory>",
const LinearSolver* solver = nullptr) {
AbaqusInputParser parser;
const auto parsed = parser.parseString(text, source_name);
if (!parsed.ok()) {
AnalysisResult result;
result.diagnostics = parsed.diagnostics;
return result;
}
LinearStaticAnalysis analysis(solver);
return analysis.run(parsed.domain);
}
} // namespace fesa