#pragma once #include "fesa/Boundary/Boundary.hpp" #include "fesa/Core/Core.hpp" #include "fesa/Element/Element.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fesa { inline SparsePattern buildReducedSparsePattern(const Domain& domain, const DofManager& dofs) { SparsePattern pattern; pattern.equation_count = dofs.freeDofCount(); std::set> 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 recoverFullReaction(const DenseMatrix& k_full, const std::vector& u_full, const std::vector& f_full) { if (k_full.rows() != k_full.cols() || static_cast(u_full.size()) != k_full.cols() || static_cast(f_full.size()) != k_full.rows()) { throw std::runtime_error("full reaction size mismatch"); } std::vector reaction = k_full.multiply(u_full); for (std::size_t i = 0; i < reaction.size(); ++i) { reaction[i] -= f_full[i]; } return reaction; } struct MITC4MaterialMatrixEvaluation { MITC4MaterialMatrix matrix{}; std::vector diagnostics; bool ok() const { return !hasError(diagnostics); } }; struct MITC4StrainTransform { MITC4MaterialMatrix matrix{}; std::vector 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 diagnostics; bool ok() const { return !hasError(diagnostics); } }; struct MITC4MaterialIntegrationData { std::vector samples; std::vector diagnostics; bool ok() const { return !hasError(diagnostics); } }; inline std::array mitc4GaussQuadrature2x2x2() { const Real gauss = 1.0 / std::sqrt(3.0); const std::array points = {-gauss, gauss}; std::array 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, 3> mitc4TensorFromEngineeringComponent(std::size_t component) { std::array, 3> tensor{}; switch (static_cast(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, 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 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 local = {basis.local.e1, basis.local.e2, basis.local.e3}; std::array, 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, 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; } 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 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 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(node)]; const std::array axes = {frame.v1, frame.v2, frame.vn}; for (LocalIndex local_axis = 0; local_axis < 3; ++local_axis) { const Vec3 axis = axes[static_cast(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(global_dof)] * local_dof_transform(local_dof, global_dof); } local_rows.rows[component][static_cast(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(i)] * material[a][b] * rows.rows[b][static_cast(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::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& 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 mitc4ElementInternalForce(const MITC4ElementStiffnessResult& stiffness, const std::vector& element_displacement) { if (stiffness.global.rows() != static_cast(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& 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 f_full; SparsePattern reduced_pattern; std::vector diagnostics; bool ok() const { return !hasError(diagnostics); } }; struct ReducedSystem { DenseMatrix k; std::vector f; std::vector free_full_indices; std::vector 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(static_cast(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 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(a)]; for (LocalIndex b = 0; b < 24; ++b) { const LocalIndex ib = element_full_indices[static_cast(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(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(static_cast(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(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(i)]; result.f[static_cast(i)] = assembly.f_full[static_cast(full_i)]; for (LocalIndex j = 0; j < dofs.freeDofCount(); ++j) { const LocalIndex full_j = dofs.freeFullIndices()[static_cast(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 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(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 = "", 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