diff --git a/.gitignore b/.gitignore index ac8e235..f09ed83 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,5 @@ __pycache__/ .pytest_cache/ *.py[cod] .harness/build/ -*pytest-basetemp*/ out/ .worktrees/ diff --git a/include/fesa/constraints/essential_bc.hpp b/include/fesa/constraints/essential_bc.hpp index a95c8d8..9d0675b 100644 --- a/include/fesa/constraints/essential_bc.hpp +++ b/include/fesa/constraints/essential_bc.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -8,15 +7,12 @@ #include #include #include -#include namespace fesa { struct ReducedSystem final { SymmetricCsr stiffness; std::vector force; - std::vector free_to_full; - std::vector prescribed_full; }; struct ConstraintResult final { @@ -26,8 +22,7 @@ struct ConstraintResult final { [[nodiscard]] ConstraintResult eliminate_essential_bcs( const EquationSystem& original, - const DofManager& dofs, - std::span prescribed); + const DofManager& dofs); [[nodiscard]] std::vector recover_reaction( const EquationSystem& original, diff --git a/include/fesa/fem/dof_manager.hpp b/include/fesa/fem/dof_manager.hpp index 341ae55..b7b16c3 100644 --- a/include/fesa/fem/dof_manager.hpp +++ b/include/fesa/fem/dof_manager.hpp @@ -34,8 +34,12 @@ public: [[nodiscard]] std::size_t free_equation_count() const noexcept; [[nodiscard]] std::optional equation( DofAddress address) const; + [[nodiscard]] std::optional equation( + std::size_t full_dof) const; [[nodiscard]] std::optional prescribed_value( DofAddress address) const; + [[nodiscard]] std::optional prescribed_value( + std::size_t full_dof) const; [[nodiscard]] std::size_t full_dof(DofAddress address) const; [[nodiscard]] std::array element_full_dofs( const BeamElement& element) const; diff --git a/include/fesa/solvers/linear/pardiso_linear_solver.hpp b/include/fesa/solvers/linear/pardiso_linear_solver.hpp index d7de39e..541e57a 100644 --- a/include/fesa/solvers/linear/pardiso_linear_solver.hpp +++ b/include/fesa/solvers/linear/pardiso_linear_solver.hpp @@ -10,6 +10,9 @@ class PardisoLinearSolver final : public LinearSolver { public: PardisoLinearSolver(); ~PardisoLinearSolver() override; + PardisoLinearSolver(const PardisoLinearSolver&) = delete; + PardisoLinearSolver& operator=( + const PardisoLinearSolver&) = delete; [[nodiscard]] LinearSolveResult solve( const SymmetricCsr& matrix, diff --git a/src/fesa/assembly/serial_assembler.cpp b/src/fesa/assembly/serial_assembler.cpp index 0583aec..0904da2 100644 --- a/src/fesa/assembly/serial_assembler.cpp +++ b/src/fesa/assembly/serial_assembler.cpp @@ -47,7 +47,13 @@ SymmetricCsr build_sparsity_pattern( const DofManager& dofs) { using Coordinate = std::pair; std::vector coordinates; - coordinates.reserve(domain.beam_elements().size() * 78); + coordinates.reserve( + dofs.full_dof_count() + + domain.beam_elements().size() * 78); + + for (std::size_t full = 0; full < dofs.full_dof_count(); ++full) { + coordinates.emplace_back(full, full); + } for (const BeamElement& element : domain.beam_elements()) { const std::array full_dofs = diff --git a/src/fesa/constraints/essential_bc.cpp b/src/fesa/constraints/essential_bc.cpp index 1c327bb..8c3a870 100644 --- a/src/fesa/constraints/essential_bc.cpp +++ b/src/fesa/constraints/essential_bc.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -33,6 +34,16 @@ std::optional validate_system( if (matrix.column_indices.size() != matrix.values.size()) { return "CSR column and value counts must match."; } + if (!std::ranges::all_of( + matrix.values, [](const double value) { + return std::isfinite(value); + }) || + !std::ranges::all_of( + system.force, [](const double value) { + return std::isfinite(value); + })) { + return "Matrix and force values must be finite."; + } std::int32_t previous_offset = 0; for (const std::int32_t offset : matrix.row_offsets) { @@ -92,8 +103,7 @@ std::int32_t csr_index(const std::size_t value) { ConstraintResult eliminate_essential_bcs( const EquationSystem& original, - const DofManager& dofs, - const std::span prescribed) { + const DofManager& dofs) { ConstraintResult result; if (const auto error = validate_system(original); error.has_value()) { @@ -109,91 +119,31 @@ ConstraintResult eliminate_essential_bcs( } const std::size_t full_count = original.stiffness.order; - std::vector constrained(full_count, false); - std::vector prescribed_full(full_count, 0.0); - for (const PrescribedDof& value : prescribed) { - if (value.dof < 1 || value.dof > 6 || - !std::isfinite(value.value)) { - result.diagnostics.push_back(equation_error( - "equation.invalid_prescribed_dof", - "Prescribed DOF data must contain a DOF in [1, 6] " - "and a finite value.")); - continue; - } - - const DofAddress address{ - value.node, - static_cast(value.dof - std::uint8_t{1}), - }; - std::size_t full = 0; - std::optional owned_value; - try { - full = dofs.full_dof(address); - owned_value = dofs.prescribed_value(address); - } catch (const std::out_of_range&) { - result.diagnostics.push_back(equation_error( - "equation.invalid_prescribed_dof", - "Prescribed DOF references a node absent from the " - "DofManager.")); - continue; - } - - if (constrained[full]) { - result.diagnostics.push_back(equation_error( - "equation.conflicting_prescribed_dof", - "A full DOF has more than one prescribed value.")); - continue; - } - constrained[full] = true; - - if (!owned_value.has_value() || - *owned_value != value.value) { - result.diagnostics.push_back(equation_error( - "equation.prescribed_dof_mismatch", - "Prescribed DOFs must match the state owned by the " - "DofManager.")); - continue; - } - prescribed_full[full] = *owned_value; - } - - const std::size_t constrained_count = - full_count - dofs.free_equation_count(); - if (prescribed.size() != constrained_count) { - result.diagnostics.push_back(equation_error( - "equation.prescribed_dof_mismatch", - "Prescribed DOF count must match the DofManager " - "constraint state.")); - } - if (!result.diagnostics.empty()) { - return result; - } - std::vector full_to_free(full_count, full_count); - std::vector free_to_full; - free_to_full.reserve(dofs.free_equation_count()); + std::vector prescribed_full(full_count, 0.0); for (std::size_t full = 0; full < full_count; ++full) { - if (!constrained[full]) { - full_to_free[full] = free_to_full.size(); - free_to_full.push_back(full); + const std::optional equation = + dofs.equation(full); + if (equation.has_value()) { + full_to_free[full] = *equation; + } else { + prescribed_full[full] = + dofs.prescribed_value(full).value(); } } - if (free_to_full.size() != dofs.free_equation_count()) { - result.diagnostics.push_back(equation_error( - "equation.prescribed_dof_mismatch", - "Prescribed DOFs do not identify the DofManager constraints.")); - return result; - } std::vector reduced_force( - free_to_full.size(), 0.0); - for (std::size_t free = 0; free < free_to_full.size(); ++free) { - reduced_force[free] = original.force[free_to_full[free]]; + dofs.free_equation_count(), 0.0); + for (std::size_t full = 0; full < full_count; ++full) { + if (full_to_free[full] != full_count) { + reduced_force[full_to_free[full]] = original.force[full]; + } } SymmetricCsr reduced{ - free_to_full.size(), - std::vector(free_to_full.size() + 1, 0), + dofs.free_equation_count(), + std::vector( + dofs.free_equation_count() + 1, 0), {}, {}, }; @@ -212,12 +162,12 @@ ConstraintResult eliminate_essential_bcs( original.stiffness.column_indices[entry]); const double stiffness = original.stiffness.values[entry]; - if (constrained[full_row]) { - if (!constrained[full_column]) { + if (full_to_free[full_row] == full_count) { + if (full_to_free[full_column] != full_count) { reduced_force[full_to_free[full_column]] -= stiffness * prescribed_full[full_row]; } - } else if (constrained[full_column]) { + } else if (full_to_free[full_column] == full_count) { reduced_force[full_to_free[full_row]] -= stiffness * prescribed_full[full_column]; } else { @@ -226,17 +176,24 @@ ConstraintResult eliminate_essential_bcs( reduced.values.push_back(stiffness); } } - if (!constrained[full_row]) { + if (full_to_free[full_row] != full_count) { reduced.row_offsets[full_to_free[full_row] + 1] = csr_index(reduced.column_indices.size()); } } + if (!std::ranges::all_of( + reduced_force, [](const double value) { + return std::isfinite(value); + })) { + result.diagnostics.push_back(equation_error( + "equation.nonfinite_result", + "Essential BC elimination produced a nonfinite force.")); + return result; + } result.reduced_system = ReducedSystem{ std::move(reduced), std::move(reduced_force), - std::move(free_to_full), - std::move(prescribed_full), }; return result; } @@ -252,6 +209,13 @@ std::vector recover_reaction( throw std::invalid_argument{ "Full displacement size must equal the matrix order."}; } + if (!std::ranges::all_of( + full_displacement, [](const double value) { + return std::isfinite(value); + })) { + throw std::invalid_argument{ + "Full displacement values must be finite."}; + } std::vector reaction = original.force; for (double& value : reaction) { @@ -277,6 +241,13 @@ std::vector recover_reaction( } } } + if (!std::ranges::all_of( + reaction, [](const double value) { + return std::isfinite(value); + })) { + throw std::invalid_argument{ + "Reaction recovery produced a nonfinite value."}; + } return reaction; } diff --git a/src/fesa/fem/dof_manager.cpp b/src/fesa/fem/dof_manager.cpp index edebd02..c223454 100644 --- a/src/fesa/fem/dof_manager.cpp +++ b/src/fesa/fem/dof_manager.cpp @@ -60,16 +60,25 @@ std::size_t DofManager::free_equation_count() const noexcept { std::optional DofManager::equation( const DofAddress address) const { - return equations_[full_dof(address)]; + return equation(full_dof(address)); +} + +std::optional DofManager::equation( + const std::size_t full_dof) const { + return equations_.at(full_dof); } std::optional DofManager::prescribed_value( const DofAddress address) const { - const std::size_t full = full_dof(address); - if (equations_[full].has_value()) { + return prescribed_value(full_dof(address)); +} + +std::optional DofManager::prescribed_value( + const std::size_t full_dof) const { + if (equations_.at(full_dof).has_value()) { return std::nullopt; } - return prescribed_values_[full]; + return prescribed_values_.at(full_dof); } std::array DofManager::element_full_dofs( diff --git a/src/fesa/solvers/linear/pardiso_linear_solver.cpp b/src/fesa/solvers/linear/pardiso_linear_solver.cpp index 172a2c5..386063e 100644 --- a/src/fesa/solvers/linear/pardiso_linear_solver.cpp +++ b/src/fesa/solvers/linear/pardiso_linear_solver.cpp @@ -106,9 +106,14 @@ public: PardisoSession& operator=(const PardisoSession&) = delete; ~PardisoSession() noexcept { + static_cast(release()); + } + + MKL_INT release() noexcept { if (!active_) { - return; + return 0; } + active_ = false; constexpr MKL_INT release_all = -1; MKL_INT error = 0; @@ -129,6 +134,7 @@ public: right_hand_side_->data(), solution_->data(), &error); + return error; } MKL_INT execute( @@ -260,37 +266,45 @@ LinearSolveResult PardisoLinearSolver::solve( PardisoSession session; constexpr MKL_INT analyze = 11; - if (const MKL_INT error = - session.execute( - analyze, matrix, right_hand_side, solution); - error != 0) { + MKL_INT error = session.execute( + analyze, matrix, right_hand_side, solution); + if (error != 0) { result.diagnostics.push_back(solver_error( "solver.analysis_failed", pardiso_failure("analysis", error))); - return result; } - constexpr MKL_INT factorize = 22; - if (const MKL_INT error = - session.execute( - factorize, matrix, right_hand_side, solution); - error != 0) { - result.diagnostics.push_back(solver_error( - "solver.factorization_failed", - pardiso_failure("factorization", error))); - return result; + if (error == 0) { + constexpr MKL_INT factorize = 22; + error = session.execute( + factorize, matrix, right_hand_side, solution); + if (error != 0) { + result.diagnostics.push_back(solver_error( + "solver.factorization_failed", + pardiso_failure("factorization", error))); + } } - constexpr MKL_INT solve_system = 33; - if (const MKL_INT error = - session.execute( - solve_system, matrix, right_hand_side, solution); - error != 0) { - result.diagnostics.push_back(solver_error( - "solver.solve_failed", - pardiso_failure("solve", error))); - return result; + if (error == 0) { + constexpr MKL_INT solve_system = 33; + error = session.execute( + solve_system, matrix, right_hand_side, solution); + if (error != 0) { + result.diagnostics.push_back(solver_error( + "solver.solve_failed", + pardiso_failure("solve", error))); + } } + + if (const MKL_INT release_error = session.release(); + release_error != 0) { + result.diagnostics.push_back(solver_error( + "solver.release_failed", + pardiso_failure("release", release_error))); + } + } + if (!result.diagnostics.empty()) { + return result; } if (!std::ranges::all_of(solution, [](const double value) { return std::isfinite(value); diff --git a/tests/unit/assembly/serial_assembler_test.cpp b/tests/unit/assembly/serial_assembler_test.cpp index fd1b532..257fb8b 100644 --- a/tests/unit/assembly/serial_assembler_test.cpp +++ b/tests/unit/assembly/serial_assembler_test.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -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(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 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(index)}, + fesa::EntityOrigin{ + "BeamPart", + "Beam-1", + static_cast((index + 1) * 10), + }, + {fesa::NodeId{4}, fesa::NodeId{10}}, + fesa::MaterialId{static_cast(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(row)); + ASSERT_NE(diagonal, end); + EXPECT_EQ(*diagonal, static_cast(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(origin_order_value), + std::bit_cast(expected)); + EXPECT_EQ( + std::bit_cast(storage_order_value), + std::bit_cast(expected)); +} + TEST(SerialAssembly, IsIndependentOfDomainStorageAndExternalLabelOrder) { const fesa::Domain first_domain = build_chain_domain(false); const fesa::Domain second_domain = build_chain_domain(true); diff --git a/tests/unit/constraints/essential_bc_test.cpp b/tests/unit/constraints/essential_bc_test.cpp index 49674a8..c47d2f8 100644 --- a/tests/unit/constraints/essential_bc_test.cpp +++ b/tests/unit/constraints/essential_bc_test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -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{5.0, -3.0, 0.0, 0.0, 0.0})); - EXPECT_EQ( - reduced.free_to_full, - (std::vector{0, 2, 3, 4, 5})); - EXPECT_EQ( - reduced.prescribed_full, - (std::vector{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{1.0, 2.0, 3.0, 4.0, 5.0, 6.0})); EXPECT_EQ( dofs.reconstruct_full({}), - reduced.prescribed_full); + (std::vector{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::quiet_NaN(); + fesa::EquationSystem nonfinite_force = hand_system(); + nonfinite_force.force[0] = + std::numeric_limits::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 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::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{5.0, 17.0, 20.0, 0.0, 0.0, 0.0})); } +TEST(Reaction, RejectsNonfiniteFullDisplacement) { + std::vector displacement(6, 0.0); + displacement[0] = std::numeric_limits::quiet_NaN(); + + EXPECT_THROW( + static_cast( + fesa::recover_reaction(hand_system(), displacement)), + std::invalid_argument); +} + +TEST(Reaction, RejectsNonfiniteRecoveredValue) { + fesa::EquationSystem overflowing = hand_system(); + overflowing.stiffness.values[0] = + std::numeric_limits::max(); + + EXPECT_THROW( + static_cast(fesa::recover_reaction( + overflowing, + std::vector{2.0, 0.0, 0.0, 0.0, 0.0, 0.0})), + std::invalid_argument); +} + } // namespace diff --git a/tests/unit/fem/dof_manager_test.cpp b/tests/unit/fem/dof_manager_test.cpp index 6dd9fc0..f60b0c3 100644 --- a/tests/unit/fem/dof_manager_test.cpp +++ b/tests/unit/fem/dof_manager_test.cpp @@ -130,6 +130,16 @@ TEST(DofManager, NumbersOnlyFreeDofsAndReconstructsPrescribedValues) { EXPECT_EQ( dofs.equation({fesa::NodeId{20}, fesa::NodeDof::ux}), std::optional{10}); + EXPECT_EQ(dofs.equation(std::size_t{0}), std::nullopt); + EXPECT_EQ( + dofs.prescribed_value(std::size_t{0}), + std::optional{1.25}); + EXPECT_EQ( + dofs.equation(std::size_t{1}), + std::optional{0}); + EXPECT_EQ( + dofs.prescribed_value(std::size_t{1}), + std::nullopt); std::vector reduced(dofs.free_equation_count()); for (std::size_t equation = 0; equation < reduced.size(); ++equation) { @@ -190,6 +200,13 @@ TEST(DofManager, RejectsInvalidAddressesAndReducedVectorSize) { static_cast( dofs.equation({fesa::NodeId{99}, fesa::NodeDof::ux})), std::out_of_range); + EXPECT_THROW( + static_cast(dofs.equation(dofs.full_dof_count())), + std::out_of_range); + EXPECT_THROW( + static_cast( + dofs.prescribed_value(dofs.full_dof_count())), + std::out_of_range); EXPECT_THROW( static_cast( dofs.reconstruct_full(std::vector(17, 0.0))), diff --git a/tests/unit/solvers/linear/pardiso_linear_solver_test.cpp b/tests/unit/solvers/linear/pardiso_linear_solver_test.cpp index dc03630..43c2428 100644 --- a/tests/unit/solvers/linear/pardiso_linear_solver_test.cpp +++ b/tests/unit/solvers/linear/pardiso_linear_solver_test.cpp @@ -6,12 +6,18 @@ #include #include #include +#include #include #include namespace { +static_assert( + !std::is_copy_constructible_v); +static_assert( + !std::is_copy_assignable_v); + fesa::SymmetricCsr spd_matrix() { return { 3,