fix(equation-and-linear-solve): address review findings
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
@@ -158,6 +159,78 @@ fesa::Domain build_parallel_domain(
|
||||
return finish_domain(std::move(builder), {"Load", {}, {}});
|
||||
}
|
||||
|
||||
fesa::Domain build_rounding_domain(const bool large_element_first) {
|
||||
fesa::DomainBuilder builder;
|
||||
builder.add_node({
|
||||
fesa::NodeId{4},
|
||||
fesa::EntityOrigin{"BeamPart", "Beam-1", 1},
|
||||
fesa::Vec3{0.0, 0.0, 0.0},
|
||||
});
|
||||
builder.add_node({
|
||||
fesa::NodeId{10},
|
||||
fesa::EntityOrigin{"BeamPart", "Beam-1", 2},
|
||||
fesa::Vec3{1.0, 0.0, 0.0},
|
||||
});
|
||||
|
||||
for (std::size_t index = 0; index < 3; ++index) {
|
||||
fesa::IsotropicElastic material = unit_material();
|
||||
material.id =
|
||||
fesa::MaterialId{static_cast<std::int64_t>(index)};
|
||||
material.name = "Material-" + std::to_string(index);
|
||||
material.young = index == 2 ? 1.0e16 : 1.0;
|
||||
builder.add_material(std::move(material));
|
||||
}
|
||||
builder.add_section(unit_section());
|
||||
|
||||
std::array<std::size_t, 3> storage_order{0, 1, 2};
|
||||
if (large_element_first) {
|
||||
storage_order = {2, 0, 1};
|
||||
}
|
||||
for (const std::size_t index : storage_order) {
|
||||
builder.add_beam_element({
|
||||
fesa::ElementId{static_cast<std::int64_t>(index)},
|
||||
fesa::EntityOrigin{
|
||||
"BeamPart",
|
||||
"Beam-1",
|
||||
static_cast<std::int64_t>((index + 1) * 10),
|
||||
},
|
||||
{fesa::NodeId{4}, fesa::NodeId{10}},
|
||||
fesa::MaterialId{static_cast<std::int64_t>(index)},
|
||||
fesa::SectionId{0},
|
||||
});
|
||||
}
|
||||
return finish_domain(std::move(builder), {"Load", {}, {}});
|
||||
}
|
||||
|
||||
fesa::Domain build_orphan_node_domain() {
|
||||
fesa::DomainBuilder builder;
|
||||
builder.add_node({
|
||||
fesa::NodeId{0},
|
||||
fesa::EntityOrigin{"BeamPart", "Beam-1", 1},
|
||||
fesa::Vec3{0.0, 0.0, 0.0},
|
||||
});
|
||||
builder.add_node({
|
||||
fesa::NodeId{1},
|
||||
fesa::EntityOrigin{"BeamPart", "Beam-1", 2},
|
||||
fesa::Vec3{1.0, 0.0, 0.0},
|
||||
});
|
||||
builder.add_node({
|
||||
fesa::NodeId{2},
|
||||
fesa::EntityOrigin{"BeamPart", "Beam-1", 3},
|
||||
fesa::Vec3{2.0, 0.0, 0.0},
|
||||
});
|
||||
builder.add_material(unit_material());
|
||||
builder.add_section(unit_section());
|
||||
builder.add_beam_element({
|
||||
fesa::ElementId{0},
|
||||
fesa::EntityOrigin{"BeamPart", "Beam-1", 1},
|
||||
{fesa::NodeId{0}, fesa::NodeId{1}},
|
||||
fesa::MaterialId{0},
|
||||
fesa::SectionId{0},
|
||||
});
|
||||
return finish_domain(std::move(builder), {"Load", {}, {}});
|
||||
}
|
||||
|
||||
double csr_value(
|
||||
const fesa::SymmetricCsr& matrix,
|
||||
std::size_t row,
|
||||
@@ -246,6 +319,29 @@ TEST(SymmetricCsr, StoresSortedUpperTriangleWithValidOffsets) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SparsePattern, IncludesZeroDiagonalForEveryOrphanNodeDof) {
|
||||
const fesa::Domain domain = build_orphan_node_domain();
|
||||
const fesa::SymmetricCsr matrix =
|
||||
fesa::assemble_serial(
|
||||
domain, fesa::DofManager::build(domain))
|
||||
.stiffness;
|
||||
|
||||
ASSERT_EQ(matrix.order, 18);
|
||||
for (std::size_t row = 0; row < matrix.order; ++row) {
|
||||
const auto begin =
|
||||
matrix.column_indices.begin() + matrix.row_offsets[row];
|
||||
const auto end =
|
||||
matrix.column_indices.begin() + matrix.row_offsets[row + 1];
|
||||
const auto diagonal = std::lower_bound(
|
||||
begin, end, static_cast<std::int32_t>(row));
|
||||
ASSERT_NE(diagonal, end);
|
||||
EXPECT_EQ(*diagonal, static_cast<std::int32_t>(row));
|
||||
}
|
||||
for (std::size_t row = 12; row < 18; ++row) {
|
||||
EXPECT_DOUBLE_EQ(csr_value(matrix, row, row), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SerialAssembly, AssemblesHandCalculatedAxialChainAndFullLoad) {
|
||||
const fesa::Domain domain = build_chain_domain(false);
|
||||
const fesa::DofManager dofs = fesa::DofManager::build(domain);
|
||||
@@ -307,6 +403,36 @@ TEST(SerialAssembly, MergesDuplicateElementContributions) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SerialAssembly, ReducesByElementOriginNotDomainStorageOrder) {
|
||||
const fesa::Domain origin_order_domain =
|
||||
build_rounding_domain(false);
|
||||
const fesa::Domain storage_order_domain =
|
||||
build_rounding_domain(true);
|
||||
|
||||
const double origin_order_value = csr_value(
|
||||
fesa::assemble_serial(
|
||||
origin_order_domain,
|
||||
fesa::DofManager::build(origin_order_domain))
|
||||
.stiffness,
|
||||
0,
|
||||
0);
|
||||
const double storage_order_value = csr_value(
|
||||
fesa::assemble_serial(
|
||||
storage_order_domain,
|
||||
fesa::DofManager::build(storage_order_domain))
|
||||
.stiffness,
|
||||
0,
|
||||
0);
|
||||
const double expected = (1.0 + 1.0) + 1.0e16;
|
||||
|
||||
EXPECT_EQ(
|
||||
std::bit_cast<std::uint64_t>(origin_order_value),
|
||||
std::bit_cast<std::uint64_t>(expected));
|
||||
EXPECT_EQ(
|
||||
std::bit_cast<std::uint64_t>(storage_order_value),
|
||||
std::bit_cast<std::uint64_t>(expected));
|
||||
}
|
||||
|
||||
TEST(SerialAssembly, IsIndependentOfDomainStorageAndExternalLabelOrder) {
|
||||
const fesa::Domain first_domain = build_chain_domain(false);
|
||||
const fesa::Domain second_domain = build_chain_domain(true);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -49,7 +50,7 @@ TEST(EssentialBc, ZeroPrescribedValueDoesNotShiftReducedForce) {
|
||||
const fesa::DofManager dofs = build_dofs(prescribed);
|
||||
|
||||
const fesa::ConstraintResult result =
|
||||
fesa::eliminate_essential_bcs(hand_system(), dofs, prescribed);
|
||||
fesa::eliminate_essential_bcs(hand_system(), dofs);
|
||||
|
||||
ASSERT_TRUE(result.reduced_system.has_value());
|
||||
EXPECT_TRUE(result.diagnostics.empty());
|
||||
@@ -69,7 +70,7 @@ TEST(
|
||||
const fesa::EquationSystem before = original;
|
||||
|
||||
const fesa::ConstraintResult result =
|
||||
fesa::eliminate_essential_bcs(original, dofs, prescribed);
|
||||
fesa::eliminate_essential_bcs(original, dofs);
|
||||
|
||||
ASSERT_TRUE(result.reduced_system.has_value());
|
||||
EXPECT_TRUE(result.diagnostics.empty());
|
||||
@@ -87,12 +88,6 @@ TEST(
|
||||
EXPECT_EQ(
|
||||
reduced.force,
|
||||
(std::vector<double>{5.0, -3.0, 0.0, 0.0, 0.0}));
|
||||
EXPECT_EQ(
|
||||
reduced.free_to_full,
|
||||
(std::vector<std::size_t>{0, 2, 3, 4, 5}));
|
||||
EXPECT_EQ(
|
||||
reduced.prescribed_full,
|
||||
(std::vector<double>{0.0, 2.0, 0.0, 0.0, 0.0, 0.0}));
|
||||
|
||||
EXPECT_EQ(original.stiffness.order, before.stiffness.order);
|
||||
EXPECT_EQ(
|
||||
@@ -122,7 +117,7 @@ TEST(ConstraintElimination, AcceptsAllDofsConstrained) {
|
||||
const fesa::DofManager dofs = build_dofs(prescribed);
|
||||
|
||||
const fesa::ConstraintResult result =
|
||||
fesa::eliminate_essential_bcs(hand_system(), dofs, prescribed);
|
||||
fesa::eliminate_essential_bcs(hand_system(), dofs);
|
||||
|
||||
ASSERT_TRUE(result.reduced_system.has_value());
|
||||
const fesa::ReducedSystem& reduced = *result.reduced_system;
|
||||
@@ -133,36 +128,51 @@ TEST(ConstraintElimination, AcceptsAllDofsConstrained) {
|
||||
EXPECT_TRUE(reduced.stiffness.column_indices.empty());
|
||||
EXPECT_TRUE(reduced.stiffness.values.empty());
|
||||
EXPECT_TRUE(reduced.force.empty());
|
||||
EXPECT_TRUE(reduced.free_to_full.empty());
|
||||
EXPECT_EQ(
|
||||
reduced.prescribed_full,
|
||||
(std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}));
|
||||
EXPECT_EQ(
|
||||
dofs.reconstruct_full({}),
|
||||
reduced.prescribed_full);
|
||||
(std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}));
|
||||
}
|
||||
|
||||
TEST(ConstraintElimination, RejectsConflictingPrescribedValues) {
|
||||
TEST(ConstraintElimination, RejectsNonfiniteSystemData) {
|
||||
const fesa::DofManager dofs = build_dofs({});
|
||||
fesa::EquationSystem nonfinite_matrix = hand_system();
|
||||
nonfinite_matrix.stiffness.values[0] =
|
||||
std::numeric_limits<double>::quiet_NaN();
|
||||
fesa::EquationSystem nonfinite_force = hand_system();
|
||||
nonfinite_force.force[0] =
|
||||
std::numeric_limits<double>::infinity();
|
||||
|
||||
const fesa::ConstraintResult matrix_result =
|
||||
fesa::eliminate_essential_bcs(nonfinite_matrix, dofs);
|
||||
const fesa::ConstraintResult force_result =
|
||||
fesa::eliminate_essential_bcs(nonfinite_force, dofs);
|
||||
|
||||
ASSERT_FALSE(matrix_result.diagnostics.empty());
|
||||
EXPECT_EQ(
|
||||
matrix_result.diagnostics.front().code,
|
||||
"equation.invalid_system");
|
||||
ASSERT_FALSE(force_result.diagnostics.empty());
|
||||
EXPECT_EQ(
|
||||
force_result.diagnostics.front().code,
|
||||
"equation.invalid_system");
|
||||
}
|
||||
|
||||
TEST(ConstraintElimination, RejectsNonfiniteReducedForce) {
|
||||
const fesa::DofManager dofs = build_dofs({
|
||||
{fesa::NodeId{0}, 2, 2.0},
|
||||
});
|
||||
const std::vector<fesa::PrescribedDof> conflicting{
|
||||
{fesa::NodeId{0}, 2, 2.0},
|
||||
{fesa::NodeId{0}, 2, 3.0},
|
||||
};
|
||||
fesa::EquationSystem overflowing = hand_system();
|
||||
overflowing.stiffness.values[1] =
|
||||
std::numeric_limits<double>::max();
|
||||
|
||||
const fesa::ConstraintResult result =
|
||||
fesa::eliminate_essential_bcs(
|
||||
hand_system(), dofs, conflicting);
|
||||
fesa::eliminate_essential_bcs(overflowing, dofs);
|
||||
|
||||
EXPECT_FALSE(result.reduced_system.has_value());
|
||||
ASSERT_FALSE(result.diagnostics.empty());
|
||||
EXPECT_NE(
|
||||
std::ranges::find(
|
||||
result.diagnostics,
|
||||
"equation.conflicting_prescribed_dof",
|
||||
&fesa::Diagnostic::code),
|
||||
result.diagnostics.end());
|
||||
EXPECT_EQ(
|
||||
result.diagnostics.front().code,
|
||||
"equation.nonfinite_result");
|
||||
}
|
||||
|
||||
TEST(Reaction, UsesOriginalFullEquilibriumEquation) {
|
||||
@@ -175,4 +185,26 @@ TEST(Reaction, UsesOriginalFullEquilibriumEquation) {
|
||||
(std::vector<double>{5.0, 17.0, 20.0, 0.0, 0.0, 0.0}));
|
||||
}
|
||||
|
||||
TEST(Reaction, RejectsNonfiniteFullDisplacement) {
|
||||
std::vector<double> displacement(6, 0.0);
|
||||
displacement[0] = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
EXPECT_THROW(
|
||||
static_cast<void>(
|
||||
fesa::recover_reaction(hand_system(), displacement)),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(Reaction, RejectsNonfiniteRecoveredValue) {
|
||||
fesa::EquationSystem overflowing = hand_system();
|
||||
overflowing.stiffness.values[0] =
|
||||
std::numeric_limits<double>::max();
|
||||
|
||||
EXPECT_THROW(
|
||||
static_cast<void>(fesa::recover_reaction(
|
||||
overflowing,
|
||||
std::vector<double>{2.0, 0.0, 0.0, 0.0, 0.0, 0.0})),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -130,6 +130,16 @@ TEST(DofManager, NumbersOnlyFreeDofsAndReconstructsPrescribedValues) {
|
||||
EXPECT_EQ(
|
||||
dofs.equation({fesa::NodeId{20}, fesa::NodeDof::ux}),
|
||||
std::optional<std::size_t>{10});
|
||||
EXPECT_EQ(dofs.equation(std::size_t{0}), std::nullopt);
|
||||
EXPECT_EQ(
|
||||
dofs.prescribed_value(std::size_t{0}),
|
||||
std::optional<double>{1.25});
|
||||
EXPECT_EQ(
|
||||
dofs.equation(std::size_t{1}),
|
||||
std::optional<std::size_t>{0});
|
||||
EXPECT_EQ(
|
||||
dofs.prescribed_value(std::size_t{1}),
|
||||
std::nullopt);
|
||||
|
||||
std::vector<double> reduced(dofs.free_equation_count());
|
||||
for (std::size_t equation = 0; equation < reduced.size(); ++equation) {
|
||||
@@ -190,6 +200,13 @@ TEST(DofManager, RejectsInvalidAddressesAndReducedVectorSize) {
|
||||
static_cast<void>(
|
||||
dofs.equation({fesa::NodeId{99}, fesa::NodeDof::ux})),
|
||||
std::out_of_range);
|
||||
EXPECT_THROW(
|
||||
static_cast<void>(dofs.equation(dofs.full_dof_count())),
|
||||
std::out_of_range);
|
||||
EXPECT_THROW(
|
||||
static_cast<void>(
|
||||
dofs.prescribed_value(dofs.full_dof_count())),
|
||||
std::out_of_range);
|
||||
EXPECT_THROW(
|
||||
static_cast<void>(
|
||||
dofs.reconstruct_full(std::vector<double>(17, 0.0))),
|
||||
|
||||
@@ -6,12 +6,18 @@
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace {
|
||||
|
||||
static_assert(
|
||||
!std::is_copy_constructible_v<fesa::PardisoLinearSolver>);
|
||||
static_assert(
|
||||
!std::is_copy_assignable_v<fesa::PardisoLinearSolver>);
|
||||
|
||||
fesa::SymmetricCsr spd_matrix() {
|
||||
return {
|
||||
3,
|
||||
|
||||
Reference in New Issue
Block a user