test(tests): added test suite
The test suite is extensive. Note that LLMs were used to aid in the development of the test suite. All LLM generated code was manually reviewed.
This commit is contained in:
385
tests/model/runtime.cpp
Normal file
385
tests/model/runtime.cpp
Normal file
@@ -0,0 +1,385 @@
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||
#include "mfem/linalg/densemat.hpp"
|
||||
#include "serif/backend/mfem/model/system.hpp"
|
||||
|
||||
namespace {
|
||||
namespace backend = serif::model::backend::mfem;
|
||||
using Catch::Matchers::WithinAbs;
|
||||
|
||||
/** @brief Make small independent matrices whose entries reveal coordinate permutations. */
|
||||
[[nodiscard]] backend::LinearizedBlock matrix(int rows, int columns, std::initializer_list<double> entries) {
|
||||
auto values = std::make_shared<::mfem::DenseMatrix>(rows, columns);
|
||||
auto entry = entries.begin();
|
||||
for (int row = 0; row < rows; ++row) {
|
||||
for (int column = 0; column < columns; ++column) {
|
||||
(*values)(row, column) = *entry++;
|
||||
}
|
||||
}
|
||||
return serif::operators::backend::mfem::linearized_block(std::shared_ptr<const ::mfem::DenseMatrix>(values));
|
||||
}
|
||||
|
||||
/** @brief Compare all coordinate entries, independent of the implementation's block selection. */
|
||||
void check_vector(const ::mfem::Vector& actual, std::initializer_list<double> expected) {
|
||||
REQUIRE(actual.Size() == static_cast<int>(expected.size()));
|
||||
int index = 0;
|
||||
for (const double value : expected) {
|
||||
CHECK_THAT(actual[index++], WithinAbs(value, 1.0e-13));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Model runtime adds generated physics before replacing complete boundary rows", "[model][runtime][unit]") {
|
||||
const auto accepted = backend::make_physical_system(4, 5,
|
||||
{{0, {1.0, 2.0, 3.0}}, {1, {10.0, 20.0, 30.0}}},
|
||||
{{0, 2, matrix(3, 3, {1, 2, 3, 4, 5, 6, 7, 8, 9})},
|
||||
{1, 0, matrix(3, 2, {10, 11, 12, 13, 14, 15})},
|
||||
{0, 1, matrix(4, 1, {20, 21, 22, 23})}},
|
||||
{{{3, 1}, {300, 100}, {{2, matrix(2, 3, {2, 3, 4, 5, 6, 7})}}},
|
||||
{{0}, {400}, {{0, matrix(1, 1, {8})}}}});
|
||||
CHECK(accepted->residual() == std::vector<double>{400, 100, 23, 300});
|
||||
const auto& jacobian = accepted->jacobian();
|
||||
REQUIRE(jacobian.has_assembly());
|
||||
CHECK_FALSE(jacobian.has_diagonal());
|
||||
const auto assembled = jacobian.assemble();
|
||||
const std::array<std::array<double, 5>, 4> expected{{{8, 0, 0, 0, 0}, {0, 0, 5, 6, 7}, {12, 35, 7, 8, 9}, {0, 0, 2, 3, 4}}};
|
||||
for (int row = 0; row < 4; ++row) {
|
||||
for (int column = 0; column < 5; ++column) {
|
||||
CHECK_THAT((*assembled)(row, column), WithinAbs(expected[row][column], 1.0e-14));
|
||||
}
|
||||
}
|
||||
::mfem::Vector direction(5), dual(4), action, transpose;
|
||||
for (int index = 0; index < 5; ++index) {
|
||||
direction[index] = index + 1;
|
||||
}
|
||||
for (int index = 0; index < 4; ++index) {
|
||||
dual[index] = 0.25 + index;
|
||||
}
|
||||
jacobian.action()->Mult(direction, action);
|
||||
check_vector(action, {8, 74, 180, 38});
|
||||
jacobian.action()->MultTranspose(dual, transpose);
|
||||
CHECK_THAT(dual * action, WithinAbs(direction * transpose, 1.0e-12));
|
||||
const auto selected = accepted->block(1, 2, 2, 2);
|
||||
CHECK(selected.has_diagonal());
|
||||
CHECK(selected.diagonal() == std::vector<double>{5, 8});
|
||||
::mfem::Vector local(2), selected_action;
|
||||
local[0] = 2;
|
||||
local[1] = 3;
|
||||
selected.action()->Mult(local, selected_action);
|
||||
check_vector(selected_action, {28, 38});
|
||||
}
|
||||
|
||||
TEST_CASE("Model selected blocks retain owners and skip unrelated equation actions", "[model][runtime][unit]") {
|
||||
auto calls = std::make_shared<int>(0);
|
||||
auto owner = std::make_shared<int>(37);
|
||||
std::weak_ptr<const int> lifetime = owner;
|
||||
const auto unrelated = serif::operators::backend::mfem::linearized_action(1, 1, [calls](const auto& input, auto& output) {
|
||||
++*calls;
|
||||
output[0] = input[0];
|
||||
}, [calls](const auto& input, auto& output) {
|
||||
++*calls;
|
||||
output[0] = input[0];
|
||||
});
|
||||
auto accepted = backend::make_physical_system(3, 3, {}, {{0, 0, matrix(2, 2, {2, 1, -1, 3})}, {2, 2, unrelated}}, {}, owner);
|
||||
std::optional<backend::LinearizedBlock> selected = accepted->block(0, 2, 0, 2);
|
||||
owner.reset();
|
||||
accepted.reset();
|
||||
CHECK_FALSE(lifetime.expired());
|
||||
::mfem::Vector input(2), output;
|
||||
input = 1.0;
|
||||
selected->action()->Mult(input, output);
|
||||
check_vector(output, {3, 2});
|
||||
CHECK(*calls == 0);
|
||||
CHECK(selected->has_diagonal());
|
||||
CHECK(selected->has_assembly());
|
||||
CHECK(selected->diagonal() == std::vector<double>{2, 3});
|
||||
selected.reset();
|
||||
CHECK(lifetime.expired());
|
||||
}
|
||||
|
||||
TEST_CASE("Grouped model blocks preserve ordered ranges and boundary rows with one action per child", "[model][runtime][unit]") {
|
||||
const auto original = matrix(4, 5, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20});
|
||||
auto calls = std::make_shared<std::array<int, 2>>();
|
||||
const auto action = serif::operators::backend::mfem::linearized_action(4, 5, [calls, original](const auto& input, auto& output) {
|
||||
++(*calls)[0];
|
||||
original.action()->Mult(input, output);
|
||||
}, [calls, original](const auto& input, auto& output) {
|
||||
++(*calls)[1];
|
||||
original.action()->MultTranspose(input, output);
|
||||
});
|
||||
const backend::LinearizedBlock counted(action.action(), {}, [original]() {
|
||||
return original.assemble();
|
||||
});
|
||||
auto unrelated_calls = std::make_shared<int>(0);
|
||||
const auto unrelated_action = [unrelated_calls](const auto& input, auto& output) {
|
||||
++*unrelated_calls;
|
||||
output = input;
|
||||
};
|
||||
auto owner = std::make_shared<int>(42);
|
||||
std::weak_ptr<const int> lifetime = owner;
|
||||
auto accepted = backend::make_physical_system(5, 6, {}, {{0, 0, counted}, {4, 5, serif::operators::backend::mfem::linearized_action(1, 1, unrelated_action, unrelated_action)}},
|
||||
{{{1, 3}, {0.0, 0.0}, {{0, matrix(2, 5, {101, 102, 103, 104, 105, 106, 107, 108, 109, 110})}}}}, owner);
|
||||
const std::array rows{backend::CoordinateRange{2, 2}, backend::CoordinateRange{0, 2}};
|
||||
const std::array columns{backend::CoordinateRange{3, 2}, backend::CoordinateRange{0, 2}};
|
||||
std::optional<backend::LinearizedBlock> selected = accepted->block(rows, columns);
|
||||
accepted.reset();
|
||||
owner.reset();
|
||||
CHECK_FALSE(lifetime.expired());
|
||||
|
||||
const std::array<std::array<double, 4>, 4> expected{{{14, 15, 11, 12}, {109, 110, 106, 107}, {4, 5, 1, 2}, {104, 105, 101, 102}}};
|
||||
const auto assembled = selected->assemble();
|
||||
for (int row = 0; row < 4; ++row) {
|
||||
for (int column = 0; column < 4; ++column) {
|
||||
CHECK((*assembled)(row, column) == expected[row][column]);
|
||||
}
|
||||
}
|
||||
CHECK(selected->diagonal() == std::vector<double>{14, 110, 1, 102});
|
||||
CHECK(*calls == std::array<int, 2>{0, 0});
|
||||
::mfem::Vector input(4), dual(4), result, transpose;
|
||||
for (int index = 0; index < 4; ++index) {
|
||||
input[index] = index + 1;
|
||||
dual[index] = index - 0.5;
|
||||
}
|
||||
selected->action()->Mult(input, result);
|
||||
selected->action()->MultTranspose(dual, transpose);
|
||||
check_vector(result, {125, 1075, 25, 1025});
|
||||
CHECK_THAT(result * dual, WithinAbs(input * transpose, 1.0e-12));
|
||||
CHECK(*calls == std::array<int, 2>{1, 1});
|
||||
CHECK(*unrelated_calls == 0);
|
||||
selected.reset();
|
||||
CHECK(lifetime.expired());
|
||||
}
|
||||
|
||||
TEST_CASE("Grouped model selections retain native diagonals and exact transposes for repeated ranges", "[model][runtime][unit]") {
|
||||
const auto original = matrix(3, 3, {1, 2, 3, 4, 5, 6, 7, 8, 9});
|
||||
const backend::LinearizedBlock diagonal_only(original.action(), [original]() {
|
||||
return original.diagonal();
|
||||
});
|
||||
const auto accepted = backend::make_physical_system(3, 3, {}, {{0, 0, diagonal_only}});
|
||||
const std::array ranges{backend::CoordinateRange{2, 1}, backend::CoordinateRange{0, 2}, backend::CoordinateRange{2, 1}};
|
||||
const auto selected = accepted->block(ranges, ranges);
|
||||
CHECK_FALSE(selected.has_assembly());
|
||||
REQUIRE(selected.has_diagonal());
|
||||
CHECK(selected.diagonal() == std::vector<double>{9, 1, 5, 9});
|
||||
::mfem::Vector input(4), dual(4), output, transpose;
|
||||
for (int index = 0; index < 4; ++index) {
|
||||
input[index] = index + 1;
|
||||
dual[index] = 0.5 * index - 0.3;
|
||||
}
|
||||
selected.action()->Mult(input, output);
|
||||
selected.action()->MultTranspose(dual, transpose);
|
||||
check_vector(output, {83, 23, 53, 83});
|
||||
CHECK_THAT(output * dual, WithinAbs(input * transpose, 1.0e-12));
|
||||
|
||||
const auto empty = accepted->block(std::span<const backend::CoordinateRange>{}, ranges);
|
||||
CHECK(empty.action()->Height() == 0);
|
||||
CHECK(empty.action()->Width() == 4);
|
||||
empty.action()->Mult(input, output);
|
||||
CHECK(output.Size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Model runtime actions permit both shifted buffer overlaps", "[model][runtime][unit]") {
|
||||
const auto accepted = backend::make_physical_system(2, 2, {}, {{0, 0, matrix(2, 2, {2, 1, -1, 3})}});
|
||||
for (const bool transpose : {false, true}) {
|
||||
for (const bool forward_overlap : {false, true}) {
|
||||
std::array<double, 3> storage{};
|
||||
::mfem::Vector input(storage.data() + (forward_overlap ? 0 : 1), 2);
|
||||
::mfem::Vector output(storage.data() + (forward_overlap ? 1 : 0), 2);
|
||||
input[0] = 4;
|
||||
input[1] = 5;
|
||||
if (transpose) {
|
||||
accepted->jacobian().action()->MultTranspose(input, output);
|
||||
check_vector(output, {3, 19});
|
||||
}
|
||||
else {
|
||||
accepted->jacobian().action()->Mult(input, output);
|
||||
check_vector(output, {13, 11});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Model runtime rejects malformed contribution and replacement coordinates", "[model][runtime][unit]") {
|
||||
const auto unit = matrix(1, 1, {1});
|
||||
CHECK_THROWS(backend::make_physical_system(-1, 1, {}, {}));
|
||||
CHECK_THROWS(backend::make_physical_system(1, 1, {{1, {1}}}, {}));
|
||||
CHECK_THROWS(backend::make_physical_system(1, 1, {}, {{0, 1, unit}}));
|
||||
CHECK_THROWS(backend::make_physical_system(1, 1, {}, {}, {{{0, 0}, {1, 2}, {}}}));
|
||||
CHECK_THROWS(backend::make_physical_system(1, 1, {}, {}, {{{0}, {}, {}}}));
|
||||
CHECK_THROWS(backend::make_physical_system(1, 1, {}, {}, {{{1}, {0}, {}}}));
|
||||
CHECK_THROWS(backend::make_physical_system(2, 1, {}, {}, {{{0, 1}, {0, 0}, {{0, unit}}}}));
|
||||
CHECK_THROWS(backend::make_physical_system(1, 1, {{0, {std::numeric_limits<double>::quiet_NaN()}}}, {}));
|
||||
CHECK_THROWS(backend::make_physical_system(1, 1, {}, {}, {{{0}, {std::numeric_limits<double>::infinity()}, {}}}));
|
||||
const auto accepted = backend::make_physical_system(1, 2, {}, {});
|
||||
CHECK_THROWS(accepted->block(0, 2, 0, 1));
|
||||
const std::array row{backend::CoordinateRange{0, 1}};
|
||||
for (const backend::CoordinateRange invalid : {backend::CoordinateRange{-1, 1}, {0, -1}, {2, 1}, {std::numeric_limits<int>::max(), 1}}) {
|
||||
CHECK_THROWS(accepted->block(row, std::array{invalid}));
|
||||
}
|
||||
const auto wide = backend::make_physical_system(0, std::numeric_limits<int>::max(), {}, {});
|
||||
CHECK_THROWS_WITH(wide->block(std::span<const backend::CoordinateRange>{}, std::array{backend::CoordinateRange{0, std::numeric_limits<int>::max()}, backend::CoordinateRange{0, 1}}),
|
||||
Catch::Matchers::ContainsSubstring("INVALID_DOF_COUNT"));
|
||||
::mfem::Vector wrong(3), result(1);
|
||||
wrong = 0.0;
|
||||
result = 19;
|
||||
CHECK_THROWS(accepted->jacobian().action()->Mult(wrong, result));
|
||||
CHECK(result[0] == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("Model action composition preserves forward transpose and native assembly", "[model][runtime][unit]") {
|
||||
const auto left = matrix(2, 3, {1, 2, 3, 4, 5, 6});
|
||||
const auto right = matrix(3, 2, {2, -1, 3, 2, 1, 4});
|
||||
const auto product = serif::operators::backend::mfem::compose_blocks(left, right);
|
||||
const auto result = serif::operators::backend::mfem::sum_blocks(2, 2, {product, serif::operators::backend::mfem::transpose_block(product)});
|
||||
REQUIRE(result.has_assembly());
|
||||
const auto assembled = result.assemble();
|
||||
CHECK((*assembled)(0, 0) == 22);
|
||||
CHECK((*assembled)(0, 1) == 44);
|
||||
CHECK((*assembled)(1, 0) == 44);
|
||||
CHECK((*assembled)(1, 1) == 60);
|
||||
CHECK_THROWS(serif::operators::backend::mfem::compose_blocks(left, left));
|
||||
CHECK_THROWS(serif::operators::backend::mfem::sum_blocks(1, 1, {left}));
|
||||
}
|
||||
|
||||
TEST_CASE("Model native diagonals do not probe actions or require sparse assembly", "[model][runtime][unit]") {
|
||||
auto actions = std::make_shared<int>(0);
|
||||
auto diagonals = std::make_shared<int>(0);
|
||||
const auto action = serif::operators::backend::mfem::linearized_action(2, 2, [actions](const auto& input, auto& output) {
|
||||
++*actions;
|
||||
output = input;
|
||||
}, [actions](const auto& input, auto& output) {
|
||||
++*actions;
|
||||
output = input;
|
||||
});
|
||||
const backend::LinearizedBlock block(action.action(), [diagonals]() {
|
||||
++*diagonals;
|
||||
return std::vector<double>{1, 1};
|
||||
});
|
||||
const auto accepted = backend::make_physical_system(2, 2, {}, {{0, 0, block}, {0, 0, block}});
|
||||
REQUIRE(accepted->jacobian().has_diagonal());
|
||||
CHECK_FALSE(accepted->jacobian().has_assembly());
|
||||
CHECK(accepted->jacobian().diagonal() == std::vector<double>{2, 2});
|
||||
CHECK(*actions == 0);
|
||||
CHECK(*diagonals == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Model composition and selection reject malformed physical block callbacks", "[model][runtime][unit]") {
|
||||
const auto original = matrix(2, 2, {1, 0, 0, 1});
|
||||
const auto check_rejection = [&original](backend::LinearizedBlock invalid) {
|
||||
const auto accepted = backend::make_physical_system(2, 2, {}, {{0, 0, invalid}});
|
||||
CHECK_THROWS_WITH(accepted->jacobian().assemble(), Catch::Matchers::ContainsSubstring("MATRIX_SHAPE_ERROR"));
|
||||
CHECK_THROWS_WITH(serif::operators::backend::mfem::transpose_block(invalid).assemble(), Catch::Matchers::ContainsSubstring("MATRIX_SHAPE_ERROR"));
|
||||
CHECK_THROWS_WITH(serif::operators::backend::mfem::compose_blocks(original, invalid).assemble(), Catch::Matchers::ContainsSubstring("MATRIX_SHAPE_ERROR"));
|
||||
CHECK_THROWS_WITH(serif::operators::backend::mfem::compose_blocks(invalid, original).assemble(), Catch::Matchers::ContainsSubstring("MATRIX_SHAPE_ERROR"));
|
||||
CHECK_THROWS_WITH(serif::operators::backend::mfem::sum_blocks(2, 2, {original, invalid}).assemble(), Catch::Matchers::ContainsSubstring("MATRIX_SHAPE_ERROR"));
|
||||
};
|
||||
check_rejection(backend::LinearizedBlock(original.action(), {}, []() {
|
||||
return std::shared_ptr<const ::mfem::SparseMatrix>{};
|
||||
}));
|
||||
check_rejection(backend::LinearizedBlock(original.action(), {}, []() {
|
||||
return std::make_shared<const ::mfem::SparseMatrix>(2);
|
||||
}));
|
||||
check_rejection(backend::LinearizedBlock(original.action(), {}, []() {
|
||||
return matrix(1, 2, {1, 0}).assemble();
|
||||
}));
|
||||
const backend::LinearizedBlock invalid_diagonal(original.action(), []() {
|
||||
return std::vector<double>{1};
|
||||
});
|
||||
const auto accepted = backend::make_physical_system(2, 2, {}, {{0, 0, invalid_diagonal}});
|
||||
CHECK_THROWS_WITH(accepted->jacobian().diagonal(), Catch::Matchers::ContainsSubstring("MATRIX_SHAPE_ERROR"));
|
||||
CHECK_THROWS_WITH(serif::operators::backend::mfem::transpose_block(invalid_diagonal).diagonal(), Catch::Matchers::ContainsSubstring("MATRIX_SHAPE_ERROR"));
|
||||
CHECK_THROWS_WITH(serif::operators::backend::mfem::sum_blocks(2, 2, {original, invalid_diagonal}).diagonal(), Catch::Matchers::ContainsSubstring("MATRIX_SHAPE_ERROR"));
|
||||
}
|
||||
|
||||
TEST_CASE("Model invalid callback outputs never overwrite caller storage", "[model][runtime][unit]") {
|
||||
const auto transpose = [](const auto& input, auto& output) {
|
||||
output = input;
|
||||
};
|
||||
const auto wrong_size = serif::operators::backend::mfem::linearized_action(1, 1, [](const auto&, auto& output) {
|
||||
output.SetSize(0);
|
||||
}, transpose);
|
||||
const auto nonfinite = serif::operators::backend::mfem::linearized_action(1, 1, [](const auto&, auto& output) {
|
||||
output[0] = std::numeric_limits<double>::infinity();
|
||||
}, transpose);
|
||||
::mfem::Vector input(1), output(1);
|
||||
input = 1.0;
|
||||
output = 42.0;
|
||||
CHECK_THROWS(wrong_size.action()->Mult(input, output));
|
||||
CHECK(output[0] == 42.0);
|
||||
CHECK_THROWS(nonfinite.action()->Mult(input, output));
|
||||
CHECK(output[0] == 42.0);
|
||||
input = std::numeric_limits<double>::quiet_NaN();
|
||||
CHECK_THROWS(nonfinite.action()->MultTranspose(input, output));
|
||||
CHECK(output[0] == 42.0);
|
||||
}
|
||||
|
||||
TEST_CASE("Complete model actions traverse each equation once while selected blocks keep their native representation", "[model][runtime][unit]") {
|
||||
auto calls = std::make_shared<std::array<int, 4>>();
|
||||
const auto counted = [calls](backend::LinearizedBlock block, const int index) {
|
||||
const auto action = serif::operators::backend::mfem::linearized_action(block.action()->Height(), block.action()->Width(),
|
||||
[calls, block, index](const auto& input, auto& output) {
|
||||
++(*calls)[index];
|
||||
block.action()->Mult(input, output);
|
||||
},
|
||||
[calls, block, index](const auto& input, auto& output) {
|
||||
++(*calls)[index];
|
||||
block.action()->MultTranspose(input, output);
|
||||
});
|
||||
return backend::LinearizedBlock(action.action(), {}, [block]() {
|
||||
return block.assemble();
|
||||
});
|
||||
};
|
||||
const auto left = counted(matrix(3, 2, {1, 2, 3, 4, 5, 6}), 0);
|
||||
const auto right = counted(matrix(3, 2, {7, 8, 9, 10, 11, 12}), 1);
|
||||
const auto complete_matrix = matrix(3, 4, {1, 2, 7, 8, 3, 4, 9, 10, 5, 6, 11, 12});
|
||||
const auto complete = serif::operators::backend::mfem::linearized_action(3, 4, [calls, complete_matrix](const auto& input, auto& output) {
|
||||
++(*calls)[2];
|
||||
complete_matrix.action()->Mult(input, output);
|
||||
}, [calls, complete_matrix](const auto& input, auto& output) {
|
||||
++(*calls)[3];
|
||||
complete_matrix.action()->MultTranspose(input, output);
|
||||
});
|
||||
const auto accepted = backend::make_physical_system(3, 4, {}, {{0, 0, left}, {0, 2, right}},
|
||||
{{{1}, {0.0}, {{0, matrix(1, 4, {2, 3, 4, 5})}}}}, {},
|
||||
std::vector<backend::JacobianContribution>{{0, 0, complete}});
|
||||
mfem::Vector direction(4), dual(3), action, transpose;
|
||||
for (int column = 0; column < 4; ++column) {
|
||||
direction[column] = column + 1;
|
||||
}
|
||||
dual[0] = 0.2;
|
||||
dual[1] = -0.7;
|
||||
dual[2] = 0.4;
|
||||
accepted->jacobian().action()->Mult(direction, action);
|
||||
accepted->jacobian().action()->MultTranspose(dual, transpose);
|
||||
check_vector(action, {58, 40, 98});
|
||||
CHECK_THAT(action * dual, WithinAbs(direction * transpose, 1.0e-12));
|
||||
CHECK(*calls == std::array<int, 4>{0, 0, 1, 1});
|
||||
REQUIRE(accepted->jacobian().has_assembly());
|
||||
const auto assembled = accepted->jacobian().assemble();
|
||||
mfem::Vector assembled_action(3), assembled_transpose(4);
|
||||
assembled->Mult(direction, assembled_action);
|
||||
assembled->MultTranspose(dual, assembled_transpose);
|
||||
assembled_action -= action;
|
||||
assembled_transpose -= transpose;
|
||||
CHECK(assembled_action.Norml2() == 0.0);
|
||||
CHECK(assembled_transpose.Norml2() < 1.0e-13);
|
||||
CHECK(*calls == std::array<int, 4>{0, 0, 1, 1});
|
||||
|
||||
const auto selected = accepted->block(0, 3, 0, 2);
|
||||
mfem::Vector local(2), selected_action;
|
||||
local[0] = 1.0;
|
||||
local[1] = 2.0;
|
||||
selected.action()->Mult(local, selected_action);
|
||||
check_vector(selected_action, {5, 8, 17});
|
||||
CHECK(*calls == std::array<int, 4>{1, 0, 1, 1});
|
||||
CHECK(accepted->block(0, 2, 0, 2).diagonal() == std::vector<double>{1, 3});
|
||||
CHECK(*calls == std::array<int, 4>{1, 0, 1, 1});
|
||||
CHECK_THROWS(backend::make_physical_system(2, 4, {}, {}, {}, {}, std::vector<backend::JacobianContribution>{{0, 0, complete}}));
|
||||
CHECK_THROWS(backend::make_physical_system(3, 3, {}, {}, {}, {}, std::vector<backend::JacobianContribution>{{0, 0, complete}}));
|
||||
}
|
||||
Reference in New Issue
Block a user