Commit 85549071 authored by Lukas Riedel's avatar Lukas Riedel

Add two unit test suites for boundary conditions

Using Google Test
parent 9c0c39eb
......@@ -14,16 +14,17 @@ dorie_add_unit_test(SOURCES test-grid-function-container.cc NAME test-grid-funct
dorie_add_unit_test(SOURCES test-interpolators.cc NAME test-interpolators)
dorie_add_unit_test(SOURCES test-scaling-adapters.cc NAME test-scaling-adapters)
dorie_add_unit_test(SOURCES test-param-factory.cc NAME test-param-factory)
dorie_add_unit_test(SOURCES test-boundary-condition.cc
NAME test-boundary-condition)
# boundary condition test
dorie_add_metaini_test(
UNIT_TEST
SOURCE test-boundary-condition.cc
BASENAME test-boundary-condition
UNIT_TEST CUSTOM_MAIN
SOURCE test-boundary-condition-manager.cc
BASENAME test-boundary-condition-manager
METAINI test-boundary-condition.mini.in
CREATED_TARGETS bc_target
)
target_link_libraries(${bc_target} spdlog)
# grid creator test
dorie_add_metaini_test(
......
#include <gtest/gtest.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <memory>
#include <yaml-cpp/yaml.h>
#include <dune/common/exceptions.hh>
#include <dune/common/float_cmp.hh>
#include <dune/common/parallel/mpihelper.hh>
#include <dune/grid/yaspgrid.hh>
#include <dune/grid/common/gridview.hh>
#include <dune/dorie/common/setup.hh>
#include <dune/dorie/common/logging.hh>
#include <dune/dorie/common/grid_creator.hh>
#include <dune/dorie/common/boundary_condition/manager.hh>
#include <dune/dorie/common/boundary_condition/factory.hh>
// Store command line arguments
int _argc;
char **_argv;
/// The traits mock-up for this test
struct Traits
{
static constexpr int dim = 2;
using RF = double;
using Grid = Dune::YaspGrid<dim>;
using GV = typename Grid::LeafGridView;
using Intersection = typename GV::Traits::Intersection;
};
/// Alias for the boundary condition type
using BC = Dune::Dorie::BoundaryCondition<typename Traits::RF>;
/* ------------------------------------------------------------------------- */
// Test BC factory creating BCs from YAML nodes
/// BCFactory fixture. Create three factories with a different mode each.
class BCFactory : public ::testing::Test
{
protected:
using Mode = Dune::Dorie::BCMMode;
template <Mode mode>
using BCF = Dune::Dorie::BoundaryConditionFactory<double, mode>;
/// Initialize the DUNE MPI Helper from the command line arguments
Dune::MPIHelper& helper = Dune::MPIHelper::instance(_argc, _argv);
std::unique_ptr<BCF<Mode::none>> _bcf_none;
std::unique_ptr<BCF<Mode::richards>> _bcf_richards;
std::unique_ptr<BCF<Mode::transport>> _bcf_transport;
void SetUp () override
{
auto [inifile, log, helper] = Dune::Dorie::Setup::init(_argc, _argv);
_bcf_none = std::make_unique<BCF<Mode::none>>(log);
_bcf_richards = std::make_unique<BCF<Mode::richards>>(log);
_bcf_transport = std::make_unique<BCF<Mode::transport>>(log);
}
void TearDown () override
{
// Remove loggers
spdlog::drop_all();
}
};
TEST_F(BCFactory, Dirichlet)
{
YAML::Node config = YAML::Load("{type: Dirichlet,"
"value: 1.0,"
"time: 0.0"
"}");
EXPECT_NO_THROW(_bcf_none->create("name", config, 1.0));
EXPECT_NO_THROW(_bcf_richards->create("name", config, 1.0));
EXPECT_THROW(_bcf_transport->create("name", config, 1.0), Dune::IOError);
config = YAML::Load("{type: Dirichlet,"
"value: 1.0,"
"time: 0.0,"
"concentration_type: water_phase"
"}");
EXPECT_NO_THROW(_bcf_none->create("name", config, 1.0));
EXPECT_NO_THROW(_bcf_richards->create("name", config, 1.0));
EXPECT_NO_THROW(_bcf_transport->create("name", config, 1.0));
config = YAML::Load("{type: Dirichlet,"
"value: 1.0,"
"time: 0.0,"
"concentration_type: none"
"}");
EXPECT_NO_THROW(_bcf_none->create("name", config, 1.0));
EXPECT_NO_THROW(_bcf_richards->create("name", config, 1.0));
EXPECT_THROW(_bcf_transport->create("name", config, 1.0), Dune::IOError);
}
TEST_F(BCFactory, Neumann)
{
YAML::Node config = YAML::Load("{type: Neumann,"
"value: 1.0,"
"time: 0.0,"
"horizontal_projection: true"
"}");
EXPECT_NO_THROW(_bcf_none->create("name", config, 1.0));
EXPECT_NO_THROW(_bcf_richards->create("name", config, 1.0));
EXPECT_NO_THROW(_bcf_transport->create("name", config, 1.0));
}
TEST_F(BCFactory, Outflow)
{
YAML::Node config = YAML::Load("{type: Outflow,"
"value: 1.0,"
"time: 0.0"
"}");
EXPECT_NO_THROW(_bcf_none->create("name", config, 1.0));
EXPECT_THROW(_bcf_richards->create("name", config, 1.0), Dune::IOError);
EXPECT_NO_THROW(_bcf_transport->create("name", config, 1.0));
}
/* ------------------------------------------------------------------------- */
// Test BC manager and loaded BC data.
/// BoundaryConditionManager fixture
/** \tparam T BCMMode wrapped into an integral constant to appear as type
*/
// template<typename T>
class BCManager : public ::testing::Test
{
protected:
/// Initialize the DUNE MPI Helper from the command line arguments
Dune::MPIHelper& helper = Dune::MPIHelper::instance(_argc, _argv);
/// Pointer to the grid
std::shared_ptr<Traits::Grid> _grid;
using BCM = Dune::Dorie::BoundaryConditionManager<
Traits, Dune::Dorie::BCMMode::none
>;
/// Pointer to the manager
std::unique_ptr<BCM> _manager;
/// Initialize
void SetUp () override
{
// Initialize ALL the things!
auto [inifile, log, helper] = Dune::Dorie::Setup::init(_argc, _argv);
// create the grid
Dune::Dorie::GridCreator<Traits::Grid> gc(inifile, helper);
_grid = gc.grid();
const auto& index_map = gc.boundary_index_map();
inifile = Dune::Dorie::Setup::prep_ini_for_richards(inifile);
const auto log_level = inifile.get<std::string>("output.logLevel");
Dune::Dorie::create_logger(Dune::Dorie::log_richards,
helper,
spdlog::level::from_str(log_level));
_manager = std::make_unique<BCM>(inifile, index_map);
}
void TearDown () override
{
// remove loggers
spdlog::drop_all();
}
};
TEST_F(BCManager, TimeSteps)
{
// check correct maximum time steps
double max_dt = 0.0;
double time_prev = 0.0;
for (auto time : {time_prev, 0.5, 1.5})
{
EXPECT_DOUBLE_EQ(time, time_prev+max_dt);
time_prev = time;
this->_manager->set_time(time);
max_dt = this->_manager->max_time_step(time);
}
EXPECT_DOUBLE_EQ(max_dt, 0.5);
// assert exception if out of interval
EXPECT_THROW(this->_manager->set_time(-0.5), Dune::InvalidStateException);
EXPECT_THROW(this->_manager->set_time(2.0), Dune::InvalidStateException);
}
TEST_F(BCManager, Neumann)
{
auto gv = _grid->leafGridView();
auto cell = *Dune::elements(gv).begin();
if (not cell.hasBoundaryIntersections()) {
ADD_FAILURE();
}
for (auto&& is : Dune::intersections(gv, cell)) {
const auto coords = is.geometry().center();
if (Dune::FloatCmp::eq(coords[1], 1.0))
{
for (auto time: {0.0, 1.0, 1.5, 1.9}) {
using namespace Dune::Dorie;
_manager->set_time(time);
const auto bc = _manager->bc(is);
EXPECT_EQ(bc->name(), "neumann");
EXPECT_EQ(bc->type(), Operator::BCType::Neumann);
EXPECT_DOUBLE_EQ(bc->evaluate(time), 1.0);
auto &time_int = bc->time_interval();
EXPECT_DOUBLE_EQ(time_int.begin, 0.0);
EXPECT_DOUBLE_EQ(time_int.end, 2.0);
auto &bc_neumann =
dynamic_cast<NeumannBoundaryCondition<double>&>(*bc);
EXPECT_TRUE(bc_neumann.horizontal_projection());
}
}
}
}
TEST_F(BCManager, Dirichlet)
{
auto gv = _grid->leafGridView();
auto cell = *Dune::elements(gv).begin();
if (not cell.hasBoundaryIntersections()) {
ADD_FAILURE();
}
for (auto&& is : Dune::intersections(gv, cell)) {
const auto coords = is.geometry().center();
if (Dune::FloatCmp::eq(coords[1], 0.0))
{
using namespace Dune::Dorie;
for (auto time: {0.5, 1.4}) {
_manager->set_time(time);
const auto bc = _manager->bc(is);
EXPECT_EQ(bc->name(), "dirichlet");
EXPECT_EQ(bc->type(), Operator::BCType::Dirichlet);
EXPECT_DOUBLE_EQ(bc->evaluate(time), time-0.5);
auto &time_int = bc->time_interval();
EXPECT_DOUBLE_EQ(time_int.begin, 0.5);
EXPECT_DOUBLE_EQ(time_int.end, 1.5);
auto &bc_dirichlet =
dynamic_cast<DirichletBoundaryCondition<double>&>(*bc);
EXPECT_EQ(bc_dirichlet.concentration_type(),
SoluteConcentration::water_phase);
}
for (auto time: {0.0, 1.5, 1.9}) {
_manager->set_time(time);
const auto bc = _manager->bc(is);
EXPECT_EQ(bc->name(), "default");
EXPECT_EQ(bc->type(), Operator::BCType::Neumann);
EXPECT_DOUBLE_EQ(bc->evaluate(time), 0.0);
}
}
}
}
TEST_F(BCManager, Outflow)
{
auto gv = _grid->leafGridView();
auto cell = *Dune::elements(gv).begin();
if (not cell.hasBoundaryIntersections()) {
ADD_FAILURE();
}
for (auto&& is : Dune::intersections(gv, cell)) {
const auto coords = is.geometry().center();
if (Dune::FloatCmp::eq(coords[1], 1.0))
{
for (auto time: {0.0, 1.0, 1.5, 1.9}) {
_manager->set_time(time);
const auto bc = _manager->bc(is);
EXPECT_EQ(bc->name(), "outflow");
EXPECT_EQ(bc->type(), Dune::Dorie::Operator::BCType::Outflow);
EXPECT_DOUBLE_EQ(bc->evaluate(time), 0.0);
auto &time_int = bc->time_interval();
EXPECT_DOUBLE_EQ(time_int.begin, 0.0);
EXPECT_DOUBLE_EQ(time_int.end, 2.0);
}
}
}
}
// --- Main Function --- //
int main(int argc, char **argv)
{
// Parse input arguments
::testing::InitGoogleTest(&argc, argv);
_argc = argc;
_argv = argv;
return RUN_ALL_TESTS();
}
#include <gtest/gtest.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#include "config.h"
#endif
#include <memory>
#include <cassert>
#include <vector>
#include <yaml-cpp/yaml.h>
#include <type_traits>
#include <string>
#include <dune/common/exceptions.hh>
#include <dune/common/float_cmp.hh>
#include <dune/common/parallel/mpihelper.hh>
#include <dune/grid/yaspgrid.hh>
#include <dune/grid/common/gridview.hh>
#include <dune/dorie/common/typedefs.hh>
#include <dune/dorie/common/boundary_condition/factory.hh>
#include <dune/dorie/common/setup.hh>
#include <dune/dorie/common/logging.hh>
#include <dune/dorie/common/grid_creator.hh>
#include <dune/dorie/common/boundary_condition/manager.hh>
using RF = double;
/// The traits mock-up for this test
struct Traits
/// Initialize the BC base class
/** \param value_end Set to True if an optional final value is to be added.
*/
template<typename BC>
BC initialize_bc (bool value_end=false)
{
static constexpr int dim = 2;
using RF = double;
using Grid = Dune::YaspGrid<dim>;
using GV = typename Grid::LeafGridView;
using Intersection = typename GV::Traits::Intersection;
};
using namespace Dune::Dorie;
TimeInterval<RF> t_int({1.0, 2.0});
const RF v_begin = 1.0;
const RF v_end = 2.0;
const std::string name = "bc";
if constexpr (std::is_same_v<BC, NeumannBoundaryCondition<RF>>)
{
if (value_end) {
return BC(name, t_int, v_begin, v_end, false);
}
else {
return BC(name, t_int, v_begin, false);
}
}
else {
if (value_end) {
return BC(name, t_int, v_begin, v_end);
}
else {
return BC(name, t_int, v_begin);
}
}
}
/// Alias for the boundary condition type
using BC = Dune::Dorie::BoundaryCondition<typename Traits::RF>;
/* ------------------------------------------------------------------------- */
// Test common features of all boundary conditions
/// Test the default boundary condition, after specs of the YAML input file
void test_default_bc (const std::shared_ptr<BC> bc,
const typename Traits::RF time)
template<typename BC>
class BoundaryCondition : public ::testing::Test
{
using namespace Dune::FloatCmp;
assert(eq(bc->evaluate(time), 0.0));
protected:
BC _bc = initialize_bc<BC>();
};
// perform a cast
using namespace Dune::Dorie;
const auto& bc_neumann = dynamic_cast<
const NeumannBoundaryCondition<double>&>(*bc);
assert(not bc_neumann.horizontal_projection());
}
using BCTypes = ::testing::Types<Dune::Dorie::NeumannBoundaryCondition<RF>,
Dune::Dorie::BoundaryCondition<RF>,
Dune::Dorie::DirichletBoundaryCondition<RF>,
Dune::Dorie::OutflowBoundaryCondition<RF>>;
TYPED_TEST_SUITE(BoundaryCondition, BCTypes);
/// Test the Neumann boundary condition, after specs of the YAML input file
void test_neumann_bc (const std::shared_ptr<BC> bc,
const typename Traits::RF time,
const bool transport_mode)
TYPED_TEST(BoundaryCondition, Initialize)
{
using namespace Dune::FloatCmp;
assert(eq(bc->evaluate(0.0), 1.0));
assert(eq(bc->evaluate(2.0), 1.0));
// perform a cast
using namespace Dune::Dorie;
const auto& bc_neumann = dynamic_cast<
const NeumannBoundaryCondition<double>&>(*bc);
assert(bc_neumann.horizontal_projection() ^ transport_mode);
const auto &time_interval = this->_bc.time_interval();
EXPECT_DOUBLE_EQ(1.0, time_interval.begin);
EXPECT_DOUBLE_EQ(2.0, time_interval.end);
EXPECT_EQ(this->_bc.name(), "bc");
}
/// Test the Dirichlet boundary condition, after specs of the YAML input file
void test_dirichlet_bc (const std::shared_ptr<BC> bc,
const typename Traits::RF time,
const bool transport_mode)
TYPED_TEST(BoundaryCondition, EvaluateThrow)
{
using namespace Dune::FloatCmp;
assert(ge(time, 0.5) and le(time, 1.5));
assert(eq(bc->evaluate(0.5), 0.0));
assert(eq(bc->evaluate(1.0), 0.5));
assert(eq(bc->evaluate(1.5), 1.0));
// perform a cast
using namespace Dune::Dorie;
const auto& bc_dirichlet = dynamic_cast<
const DirichletBoundaryCondition<double>&>(*bc);
// case: not transport
if (transport_mode) {
assert(bc_dirichlet.concentration_type()
== SoluteConcentration::water_phase);
}
else {
assert(bc_dirichlet.concentration_type()
== SoluteConcentration::none);
}
// check interval testing
try {
const auto log = Dune::Dorie::get_logger(Dune::Dorie::log_base);
log->warn("Expecting one error message about a wrong query:");
bc->evaluate(2.0);
}
catch (Dune::InvalidStateException& e) {
return;
}
// execution should not reach this point
assert(false);
// inside time interval
EXPECT_NO_THROW(this->_bc.evaluate(1.0));
EXPECT_NO_THROW(this->_bc.evaluate(1.5));
EXPECT_NO_THROW(this->_bc.evaluate(2.0));
// outside time interval
EXPECT_THROW(this->_bc.evaluate(-1.0), Dune::InvalidStateException);
EXPECT_THROW(this->_bc.evaluate(0.0), Dune::InvalidStateException);
EXPECT_THROW(this->_bc.evaluate(2.5), Dune::InvalidStateException);
}
/// Test the Outflow boundary condition
void test_outflow_bc (const std::shared_ptr<BC> bc,
const typename Traits::RF time)
TYPED_TEST(BoundaryCondition, Evaluate)
{
assert(Dune::FloatCmp::eq(bc->evaluate(time), 0.0));
EXPECT_DOUBLE_EQ(this->_bc.evaluate(1.0), 1.0);
EXPECT_DOUBLE_EQ(this->_bc.evaluate(1.5), 1.0);
EXPECT_DOUBLE_EQ(this->_bc.evaluate(2.0), 1.0);
}
/// Test a single boundary condition for a given time
/** Delegates the actual test to the respective functions which are
* specialized to the type and respective values of the BC
*/
void test_boundary_condition (const std::shared_ptr<BC> bc,
const typename Traits::RF time,
const bool transport_mode)
TEST(BoundaryCondition, EvaluateInterpolate)
{
using namespace Dune::Dorie;
const auto log = get_logger(log_base);
auto bc = initialize_bc<Dune::Dorie::BoundaryCondition<RF>>(true);
EXPECT_DOUBLE_EQ(bc.evaluate(1.0), 1.0);
EXPECT_DOUBLE_EQ(bc.evaluate(1.5), 1.5);
EXPECT_DOUBLE_EQ(bc.evaluate(2.0), 2.0);
}
const auto name = bc->name();
const auto type = bc->type();
/* ------------------------------------------------------------------------- */
// Test Dirichlet BC
log->info("Query time: {}. Testing boundary condition: {}", time, name);
if (name == "default") {
test_default_bc(bc, time);
}
else if (type == Operator::BCType::Dirichlet) {
test_dirichlet_bc(bc, time, transport_mode);
}
else if (type == Operator::BCType::Neumann) {
test_neumann_bc(bc, time, transport_mode);
}
else if (type == Operator::BCType::Outflow) {
test_outflow_bc(bc, time);
}
else {
DUNE_THROW(Dune::InvalidStateException, "Encountered unknown "
"boundary condition");
}
class Dirichlet :
public testing::TestWithParam<Dune::Dorie::SoluteConcentration>
{
protected:
using BC = Dune::Dorie::DirichletBoundaryCondition<RF>;
BC _bc = BC("bc", {1.0, 2.0}, 1.0, GetParam());
};
TEST_P(Dirichlet, SoluteConcentration)
{
EXPECT_EQ(_bc.concentration_type(), GetParam());
}
/// Iterate over boundary intersections and test the respective BC
template<typename BCManager>
void test_bcs_on_grid (const std::shared_ptr<typename Traits::Grid> grid,
const BCManager& boundary,
const typename Traits::RF time,
const bool transport_mode)
TEST_P(Dirichlet, StaticInformation)
{
const auto gv = grid->leafGridView();
EXPECT_EQ(_bc.type(), Dune::Dorie::Operator::BCType::Dirichlet);
EXPECT_EQ(_bc.type_str, "dirichlet");
}
// iterate over grid elements
for (const auto& elem : elements(gv))
{
if (not elem.hasBoundaryIntersections())
continue;
INSTANTIATE_TEST_SUITE_P(
DirichletConcentrationType,
Dirichlet,
testing::Values(Dune::Dorie::SoluteConcentration::total,
Dune::Dorie::SoluteConcentration::water_phase,
Dune::Dorie::SoluteConcentration::none));
// iterate over boundary intersections
for (const auto& is : intersections(gv, elem)) {
if (not is.boundary())
continue;
const auto bc = boundary.bc(is);
test_boundary_condition(bc, time, transport_mode);
}
}
}
/* ------------------------------------------------------------------------- */
// Test Neumann BC
/// Test if set_time properly throws
/** \return True if the test passes
*/
template<typename BCManager>
bool test_set_time_throw (BCManager& boundary)
class Neumann :
public testing::TestWithParam<bool>
{
try {
boundary.set_time(2.0);
}
catch (Dune::InvalidStateException& e) {
return true;
}
return false;
}
protected:
using BC = Dune::Dorie::NeumannBoundaryCondition<RF>;
BC _bc = BC("bc", {1.0, 2.0}, 1.0, GetParam());
};
int main(int argc, char** argv)
TEST_P(Neumann, HorizontalProjection)
{
try{