/* SPDX-FileCopyrightText: Copyright (c) 2021 - 2024, the adamantine authors. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ #define BOOST_TEST_MODULE DataAssimilator #include #include #include #include #include "main.cc" namespace tt = boost::test_tools; namespace adamantine { class DataAssimilatorTester { public: void test_constructor() { boost::property_tree::ptree database; // First checking the dealii default values DataAssimilator da0(MPI_COMM_WORLD, MPI_COMM_WORLD, 0, database); double tol = 1.0e-12; BOOST_TEST(da0._solver_control.tolerance() - 1.0e-10 == 0., tt::tolerance(tol)); BOOST_TEST(da0._solver_control.max_steps() == 100u); BOOST_TEST(da0._additional_data.max_basis_size == 30u); // Now explicitly setting them database.put("solver.convergence_tolerance", 1.0e-6); database.put("solver.max_iterations", 25); database.put("solver.max_number_of_temp_vectors", 4); DataAssimilator da1(MPI_COMM_WORLD, MPI_COMM_WORLD, 0, database); BOOST_TEST(da1._solver_control.tolerance() - 1.0e-6 == 0., tt::tolerance(tol)); BOOST_TEST(da1._solver_control.max_steps() == 25u); BOOST_TEST(da1._additional_data.max_basis_size == 4u); }; void test_calc_kalman_gain() { // Create the DoF mapping MPI_Comm communicator = MPI_COMM_WORLD; boost::property_tree::ptree database; database.put("import_mesh", false); database.put("length", 1); database.put("length_divisions", 1); database.put("height", 1); database.put("height_divisions", 1); boost::optional units_optional_database; adamantine::Geometry<2> geometry(communicator, database, units_optional_database); dealii::parallel::distributed::Triangulation<2> const &tria = geometry.get_triangulation(); dealii::FE_Q<2> fe(1); dealii::DoFHandler<2> dof_handler(tria); dof_handler.distribute_dofs(fe); unsigned int sim_size = 4; unsigned int expt_size = 2; dealii::Vector expt_vec(2); expt_vec(0) = 2.5; expt_vec(1) = 9.5; std::pair, std::vector> expt_to_dof_mapping; expt_to_dof_mapping.first.resize(2); expt_to_dof_mapping.second.resize(2); expt_to_dof_mapping.first[0] = 0; expt_to_dof_mapping.first[1] = 1; expt_to_dof_mapping.second[0] = 1; expt_to_dof_mapping.second[1] = 3; boost::property_tree::ptree solver_settings_database; DataAssimilator da(communicator, communicator, 0, solver_settings_database); da._sim_size = sim_size; da._expt_size = expt_size; da._parameter_size = 0; da._num_ensemble_members = 3; da.update_dof_mapping<2>(expt_to_dof_mapping); da.update_covariance_sparsity_pattern<2>(dof_handler, 0); // Create the simulation data std::vector> augmented_state_ensemble(3); augmented_state_ensemble[0].reinit(2); augmented_state_ensemble[0].block(0).reinit(4); augmented_state_ensemble[0].block(0)(0) = 1.0; augmented_state_ensemble[0].block(0)(1) = 3.0; augmented_state_ensemble[0].block(0)(2) = 6.0; augmented_state_ensemble[0].block(0)(3) = 9.0; augmented_state_ensemble[0].collect_sizes(); augmented_state_ensemble[1].reinit(2); augmented_state_ensemble[1].block(0).reinit(4); augmented_state_ensemble[1].block(0)(0) = 1.5; augmented_state_ensemble[1].block(0)(1) = 3.2; augmented_state_ensemble[1].block(0)(2) = 6.3; augmented_state_ensemble[1].block(0)(3) = 9.7; augmented_state_ensemble[1].collect_sizes(); augmented_state_ensemble[2].reinit(2); augmented_state_ensemble[2].block(0).reinit(4); augmented_state_ensemble[2].block(0)(0) = 1.1; augmented_state_ensemble[2].block(0)(1) = 3.1; augmented_state_ensemble[2].block(0)(2) = 6.1; augmented_state_ensemble[2].block(0)(3) = 9.1; augmented_state_ensemble[2].collect_sizes(); // Build the sparse experimental covariance matrix dealii::SparsityPattern pattern(expt_size, expt_size, 1); pattern.add(0, 0); pattern.add(1, 1); pattern.compress(); dealii::SparseMatrix R(pattern); R.add(0, 0, 0.002); R.add(1, 1, 0.001); // Create the (perturbed) innovation std::vector> perturbed_innovation(3); for (unsigned int sample = 0; sample < perturbed_innovation.size(); ++sample) { perturbed_innovation[sample].reinit(expt_size); dealii::Vector temp = da.calc_Hx(augmented_state_ensemble[sample].block(0)); for (unsigned int i = 0; i < expt_size; ++i) { perturbed_innovation[sample][i] = expt_vec[i] - temp[i]; } } perturbed_innovation[0][0] = perturbed_innovation[0][0] + 0.0008; perturbed_innovation[0][1] = perturbed_innovation[0][1] - 0.0005; perturbed_innovation[1][0] = perturbed_innovation[1][0] - 0.001; perturbed_innovation[1][1] = perturbed_innovation[1][1] + 0.0002; perturbed_innovation[2][0] = perturbed_innovation[2][0] + 0.0002; perturbed_innovation[2][1] = perturbed_innovation[2][1] - 0.0009; // Apply the Kalman gain std::vector> forecast_shift = da.apply_kalman_gain(augmented_state_ensemble, R, perturbed_innovation); double tol = 1.0e-4; // Reference solution calculated using Python BOOST_TEST(forecast_shift[0][0] == 0.21352564, tt::tolerance(tol)); BOOST_TEST(forecast_shift[0][1] == -0.14600986, tt::tolerance(tol)); BOOST_TEST(forecast_shift[0][2] == -0.02616469, tt::tolerance(tol)); BOOST_TEST(forecast_shift[0][3] == 0.45321598, tt::tolerance(tol)); BOOST_TEST(forecast_shift[1][0] == -0.27786325, tt::tolerance(tol)); BOOST_TEST(forecast_shift[1][1] == -0.32946285, tt::tolerance(tol)); BOOST_TEST(forecast_shift[1][2] == -0.31226298, tt::tolerance(tol)); BOOST_TEST(forecast_shift[1][3] == -0.24346351, tt::tolerance(tol)); BOOST_TEST(forecast_shift[2][0] == 0.12767094, tt::tolerance(tol)); BOOST_TEST(forecast_shift[2][1] == -0.20319395, tt::tolerance(tol)); BOOST_TEST(forecast_shift[2][2] == -0.09290565, tt::tolerance(tol)); BOOST_TEST(forecast_shift[2][3] == 0.34824753, tt::tolerance(tol)); }; void test_update_dof_mapping() { unsigned int sim_size = 4; unsigned int expt_size = 3; std::pair, std::vector> expt_to_dof_mapping; expt_to_dof_mapping.first.resize(3); expt_to_dof_mapping.second.resize(3); expt_to_dof_mapping.first[0] = 0; expt_to_dof_mapping.first[1] = 1; expt_to_dof_mapping.first[2] = 2; expt_to_dof_mapping.second[0] = 0; expt_to_dof_mapping.second[1] = 1; expt_to_dof_mapping.second[2] = 3; boost::property_tree::ptree solver_settings_database; DataAssimilator da(MPI_COMM_WORLD, MPI_COMM_WORLD, 0, solver_settings_database); da._sim_size = sim_size; da._expt_size = expt_size; da.update_dof_mapping<2>(expt_to_dof_mapping); BOOST_TEST(da._expt_to_dof_mapping.first[0] == 0); BOOST_TEST(da._expt_to_dof_mapping.first[1] == 1); BOOST_TEST(da._expt_to_dof_mapping.first[2] == 2); BOOST_TEST(da._expt_to_dof_mapping.second[0] == 0); BOOST_TEST(da._expt_to_dof_mapping.second[1] == 1); BOOST_TEST(da._expt_to_dof_mapping.second[2] == 3); }; void test_calc_H() { MPI_Comm communicator = MPI_COMM_WORLD; boost::property_tree::ptree database; database.put("import_mesh", false); database.put("length", 1); database.put("length_divisions", 2); database.put("height", 1); database.put("height_divisions", 2); boost::optional units_optional_database; adamantine::Geometry<2> geometry(communicator, database, units_optional_database); dealii::parallel::distributed::Triangulation<2> const &tria = geometry.get_triangulation(); dealii::FE_Q<2> fe(1); dealii::DoFHandler<2> dof_handler(tria); dof_handler.distribute_dofs(fe); unsigned int sim_size = 4; unsigned int expt_size = 3; std::pair, std::vector> expt_to_dof_mapping; expt_to_dof_mapping.first.resize(3); expt_to_dof_mapping.second.resize(3); expt_to_dof_mapping.first[0] = 0; expt_to_dof_mapping.first[1] = 1; expt_to_dof_mapping.first[2] = 2; expt_to_dof_mapping.second[0] = 0; expt_to_dof_mapping.second[1] = 1; expt_to_dof_mapping.second[2] = 3; boost::property_tree::ptree solver_settings_database; DataAssimilator da(communicator, communicator, 0, solver_settings_database); da._sim_size = sim_size; da._expt_size = expt_size; da.update_dof_mapping<2>(expt_to_dof_mapping); dealii::SparsityPattern pattern(expt_size, sim_size, expt_size); dealii::SparseMatrix H = da.calc_H(pattern); double tol = 1e-12; for (unsigned int i = 0; i < expt_size; ++i) { for (unsigned int j = 0; j < sim_size; ++j) { if (i == 0 && j == 0) BOOST_TEST(H(i, j) == 1.0, tt::tolerance(tol)); else if (i == 1 && j == 1) BOOST_TEST(H(i, j) == 1.0, tt::tolerance(tol)); else if (i == 2 && j == 3) BOOST_TEST(H(i, j) == 1.0, tt::tolerance(tol)); else BOOST_TEST(H.el(i, j) == 0.0, tt::tolerance(tol)); } } }; void test_calc_Hx() { MPI_Comm communicator = MPI_COMM_WORLD; boost::property_tree::ptree database; database.put("import_mesh", false); database.put("length", 1); database.put("length_divisions", 2); database.put("height", 1); database.put("height_divisions", 2); boost::optional units_optional_database; adamantine::Geometry<2> geometry(communicator, database, units_optional_database); dealii::parallel::distributed::Triangulation<2> const &tria = geometry.get_triangulation(); dealii::FE_Q<2> fe(1); dealii::DoFHandler<2> dof_handler(tria); dof_handler.distribute_dofs(fe); int sim_size = 4; int expt_size = 3; dealii::LA::distributed::Vector sim_vec(dof_handler.n_dofs()); sim_vec(0) = 2.0; sim_vec(1) = 4.0; sim_vec(2) = 5.0; sim_vec(3) = 7.0; dealii::Vector expt_vec(3); expt_vec(0) = 2.5; expt_vec(1) = 4.5; expt_vec(2) = 8.5; std::pair, std::vector> expt_to_dof_mapping; expt_to_dof_mapping.first.resize(3); expt_to_dof_mapping.second.resize(3); expt_to_dof_mapping.first[0] = 0; expt_to_dof_mapping.first[1] = 1; expt_to_dof_mapping.first[2] = 2; expt_to_dof_mapping.second[0] = 0; expt_to_dof_mapping.second[1] = 1; expt_to_dof_mapping.second[2] = 3; boost::property_tree::ptree solver_settings_database; DataAssimilator da(communicator, communicator, 0, solver_settings_database); da._sim_size = sim_size; da._expt_size = expt_size; da.update_dof_mapping<2>(expt_to_dof_mapping); dealii::Vector Hx = da.calc_Hx(sim_vec); double tol = 1e-10; BOOST_TEST(Hx(0) == 2.0, tt::tolerance(tol)); BOOST_TEST(Hx(1) == 4.0, tt::tolerance(tol)); BOOST_TEST(Hx(2) == 7.0, tt::tolerance(tol)); }; void test_update_covariance_sparsity_pattern() { MPI_Comm communicator = MPI_COMM_WORLD; boost::property_tree::ptree database; database.put("import_mesh", false); database.put("length", 1); database.put("length_divisions", 2); database.put("height", 1); database.put("height_divisions", 2); boost::optional units_optional_database; adamantine::Geometry<2> geometry(communicator, database, units_optional_database); dealii::parallel::distributed::Triangulation<2> const &tria = geometry.get_triangulation(); dealii::FE_Q<2> fe(1); dealii::DoFHandler<2> dof_handler(tria); dof_handler.distribute_dofs(fe); // Effectively a dense matrix boost::property_tree::ptree solver_settings_database; DataAssimilator da(communicator, communicator, 0, solver_settings_database); da._localization_cutoff_distance = 100.0; da._localization_cutoff_function = LocalizationCutoff::step_function; da.update_covariance_sparsity_pattern<2>(dof_handler, 0); BOOST_TEST(da._covariance_sparsity_pattern.n_nonzero_elements() == 81u); // Sparse diagonal matrix da._localization_cutoff_distance = 1.0e-6; da._localization_cutoff_function = LocalizationCutoff::step_function; da.update_covariance_sparsity_pattern<2>(dof_handler, 0); BOOST_TEST(da._covariance_sparsity_pattern.n_nonzero_elements() == 9u); // More general sparse matrix, cuts off interactions between the corners // of the domain da._localization_cutoff_distance = 1.2; da._localization_cutoff_function = LocalizationCutoff::step_function; da.update_covariance_sparsity_pattern<2>(dof_handler, 0); BOOST_TEST(da._covariance_sparsity_pattern.n_nonzero_elements() == 77u); // Sparse diagonal matrix with two augmentation parameters da._localization_cutoff_distance = 1.0e-6; da._localization_cutoff_function = LocalizationCutoff::step_function; da.update_covariance_sparsity_pattern<2>(dof_handler, 2); BOOST_TEST(da._covariance_sparsity_pattern.n_nonzero_elements() == 49u); } void test_calc_sample_covariance_sparse() { MPI_Comm communicator = MPI_COMM_WORLD; boost::property_tree::ptree database; database.put("import_mesh", false); database.put("length", 1); database.put("length_divisions", 1); database.put("height", 1); database.put("height_divisions", 1); boost::optional units_optional_database; adamantine::Geometry<2> geometry(communicator, database, units_optional_database); dealii::parallel::distributed::Triangulation<2> const &tria = geometry.get_triangulation(); dealii::FE_Q<2> fe(1); dealii::DoFHandler<2> dof_handler(tria); dof_handler.distribute_dofs(fe); // Trivial case of identical vectors, effectively dense, covariance should // be the zero matrix dealii::LA::distributed::BlockVector sim_vec(1, dof_handler.n_dofs()); sim_vec[0] = 2.0; sim_vec[1] = 4.0; sim_vec[2] = 5.0; sim_vec[3] = 7.0; std::vector> vec_ensemble; vec_ensemble.push_back(sim_vec); vec_ensemble.push_back(sim_vec); boost::property_tree::ptree solver_settings_database; solver_settings_database.put("localization_cutoff_distance", 100.0); solver_settings_database.put("localization_cutoff_function", "step_function"); DataAssimilator da(communicator, communicator, 0, solver_settings_database); da._sim_size = sim_vec.size(); da._num_ensemble_members = vec_ensemble.size(); da.update_covariance_sparsity_pattern<2>(dof_handler, 0); auto cov = da.calc_sample_covariance_sparse(vec_ensemble); // Check results double tol = 1e-10; for (unsigned int i = 0; i < 4; ++i) { for (unsigned int j = 0; j < 4; ++j) { BOOST_TEST(cov.el(i, j) == 0., tt::tolerance(tol)); } } // Non-trivial case, still effectively dense, using NumPy solution as the // reference dealii::LA::distributed::BlockVector sim_vec1(1, dof_handler.n_dofs()); sim_vec1(0) = 2.1; sim_vec1(1) = 4.3; sim_vec1(2) = 5.2; sim_vec1(3) = 7.4; std::vector> vec_ensemble1; vec_ensemble1.push_back(sim_vec); vec_ensemble1.push_back(sim_vec1); auto cov1 = da.calc_sample_covariance_sparse(vec_ensemble1); BOOST_TEST(cov1.el(0, 0) == 0.005, tt::tolerance(tol)); BOOST_TEST(cov1.el(0, 1) == 0.015, tt::tolerance(tol)); BOOST_TEST(cov1.el(0, 2) == 0.01, tt::tolerance(tol)); BOOST_TEST(cov1.el(0, 3) == 0.02, tt::tolerance(tol)); BOOST_TEST(cov1.el(1, 0) == 0.015, tt::tolerance(tol)); BOOST_TEST(cov1.el(1, 1) == 0.045, tt::tolerance(tol)); BOOST_TEST(cov1.el(1, 2) == 0.03, tt::tolerance(tol)); BOOST_TEST(cov1.el(1, 3) == 0.06, tt::tolerance(tol)); BOOST_TEST(cov1.el(2, 0) == 0.01, tt::tolerance(tol)); BOOST_TEST(cov1.el(2, 1) == 0.03, tt::tolerance(tol)); BOOST_TEST(cov1.el(2, 2) == 0.02, tt::tolerance(tol)); BOOST_TEST(cov1.el(2, 3) == 0.04, tt::tolerance(tol)); BOOST_TEST(cov1.el(3, 0) == 0.02, tt::tolerance(tol)); BOOST_TEST(cov1.el(3, 1) == 0.06, tt::tolerance(tol)); BOOST_TEST(cov1.el(3, 2) == 0.04, tt::tolerance(tol)); BOOST_TEST(cov1.el(3, 3) == 0.08, tt::tolerance(tol)); // Non-trivial case with step-function sparsity da._localization_cutoff_distance = 1.0e-6; da.update_covariance_sparsity_pattern<2>(dof_handler, 0); BOOST_TEST(da._covariance_sparsity_pattern.n_nonzero_elements() == 4); auto cov2 = da.calc_sample_covariance_sparse(vec_ensemble1); BOOST_TEST(cov2.el(0, 0) == 0.005, tt::tolerance(tol)); BOOST_TEST(cov2.el(0, 1) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(0, 2) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(0, 3) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(1, 0) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(1, 1) == 0.045, tt::tolerance(tol)); BOOST_TEST(cov2.el(1, 2) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(1, 3) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(2, 0) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(2, 1) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(2, 2) == 0.02, tt::tolerance(tol)); BOOST_TEST(cov2.el(2, 3) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(3, 0) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(3, 1) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(3, 2) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov2.el(3, 3) == 0.08, tt::tolerance(tol)); // Non-trivial case with Gaspari-Cohn sparsity da._localization_cutoff_distance = 3.0; da._localization_cutoff_function = LocalizationCutoff::gaspari_cohn; da.update_covariance_sparsity_pattern<2>(dof_handler, 0); BOOST_TEST(da._covariance_sparsity_pattern.n_nonzero_elements() == 16); auto cov3 = da.calc_sample_covariance_sparse(vec_ensemble1); BOOST_TEST(cov3.el(0, 0) == 0.005, tt::tolerance(tol)); BOOST_TEST(cov3.el(0, 1) > 0.0); BOOST_TEST(cov3.el(0, 1) < 0.015); BOOST_TEST(cov3.el(0, 2) > 0.0); BOOST_TEST(cov3.el(0, 2) < 0.01); BOOST_TEST(cov3.el(0, 3) > 0.0); BOOST_TEST(cov3.el(0, 3) < 0.02); BOOST_TEST(cov3.el(1, 0) > 0.0); BOOST_TEST(cov3.el(1, 0) < 0.015); BOOST_TEST(cov3.el(1, 1) == 0.045, tt::tolerance(tol)); BOOST_TEST(cov3.el(1, 2) > 0.0); BOOST_TEST(cov3.el(1, 2) < 0.03); BOOST_TEST(cov3.el(1, 3) > 0.0); BOOST_TEST(cov3.el(1, 3) < 0.06); BOOST_TEST(cov3.el(2, 0) > 0.0); BOOST_TEST(cov3.el(2, 0) < 0.01); BOOST_TEST(cov3.el(2, 1) > 0.0); BOOST_TEST(cov3.el(2, 1) < 0.03); BOOST_TEST(cov3.el(2, 2) == 0.02, tt::tolerance(tol)); BOOST_TEST(cov3.el(2, 3) > 0.0); BOOST_TEST(cov3.el(2, 3) < 0.04); BOOST_TEST(cov3.el(3, 0) > 0.0); BOOST_TEST(cov3.el(3, 0) < 0.02); BOOST_TEST(cov3.el(3, 1) > 0.0); BOOST_TEST(cov3.el(3, 1) < 0.06); BOOST_TEST(cov3.el(3, 2) > 0.0); BOOST_TEST(cov3.el(3, 2) < 0.04); BOOST_TEST(cov3.el(3, 3) == 0.08, tt::tolerance(tol)); // Non-trivial case with step-function sparsity and two augmented parameters dealii::LA::distributed::BlockVector sim_vec2( 1, dof_handler.n_dofs() + 2); sim_vec2(0) = 2.0; sim_vec2(1) = 4.0; sim_vec2(2) = 5.0; sim_vec2(3) = 7.0; sim_vec2(4) = 1.0; sim_vec2(5) = 1.5; dealii::LA::distributed::BlockVector sim_vec3( 1, dof_handler.n_dofs() + 2); sim_vec3(0) = 2.1; sim_vec3(1) = 4.3; sim_vec3(2) = 5.2; sim_vec3(3) = 7.4; sim_vec3(4) = 1.1; sim_vec3(5) = 1.4; std::vector> vec_ensemble2; vec_ensemble2.push_back(sim_vec2); vec_ensemble2.push_back(sim_vec3); da._localization_cutoff_distance = 1.0e-6; da.update_covariance_sparsity_pattern<2>(dof_handler, 2); BOOST_TEST(da._covariance_sparsity_pattern.n_nonzero_elements() == 24u); auto cov4 = da.calc_sample_covariance_sparse(vec_ensemble2); BOOST_TEST(cov4.el(0, 0) == 0.005, tt::tolerance(tol)); BOOST_TEST(cov4.el(0, 1) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(0, 2) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(0, 3) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(1, 0) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(1, 1) == 0.045, tt::tolerance(tol)); BOOST_TEST(cov4.el(1, 2) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(1, 3) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(2, 0) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(2, 1) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(2, 2) == 0.02, tt::tolerance(tol)); BOOST_TEST(cov4.el(2, 3) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(3, 0) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(3, 1) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(3, 2) == 0.0, tt::tolerance(tol)); BOOST_TEST(cov4.el(3, 3) == 0.08, tt::tolerance(tol)); BOOST_TEST(cov4.el(4, 0) == 0.005, tt::tolerance(tol)); BOOST_TEST(cov4.el(4, 1) == 0.015, tt::tolerance(tol)); BOOST_TEST(cov4.el(4, 2) == 0.01, tt::tolerance(tol)); BOOST_TEST(cov4.el(4, 3) == 0.02, tt::tolerance(tol)); BOOST_TEST(cov4.el(4, 4) == 0.005, tt::tolerance(tol)); BOOST_TEST(cov4.el(4, 5) == -0.005, tt::tolerance(tol)); BOOST_TEST(cov4.el(5, 0) == -0.005, tt::tolerance(tol)); BOOST_TEST(cov4.el(5, 1) == -0.015, tt::tolerance(tol)); BOOST_TEST(cov4.el(5, 2) == -0.01, tt::tolerance(tol)); BOOST_TEST(cov4.el(5, 3) == -0.02, tt::tolerance(tol)); BOOST_TEST(cov4.el(5, 4) == -0.005, tt::tolerance(tol)); BOOST_TEST(cov4.el(5, 5) == 0.005, tt::tolerance(tol)); }; void test_fill_noise_vector(bool R_is_diagonal) { if (R_is_diagonal) { boost::property_tree::ptree solver_settings_database; DataAssimilator da(MPI_COMM_WORLD, MPI_COMM_WORLD, 0, solver_settings_database); dealii::SparsityPattern pattern(3, 3, 1); pattern.add(0, 0); pattern.add(1, 1); pattern.add(2, 2); pattern.compress(); dealii::SparseMatrix R(pattern); R.add(0, 0, 0.1); R.add(1, 1, 1.0); R.add(2, 2, 0.2); std::vector> data; dealii::Vector ensemble_member(3); for (unsigned int i = 0; i < 1000; ++i) { da.fill_noise_vector(ensemble_member, R, R_is_diagonal); data.push_back(ensemble_member); } dealii::FullMatrix Rtest = calc_sample_covariance_dense(data); double tol = 20.; // Loose 20% tolerance because this is a statistical check BOOST_TEST(R(0, 0) == Rtest(0, 0), tt::tolerance(tol)); BOOST_TEST(R(1, 1) == Rtest(1, 1), tt::tolerance(tol)); BOOST_TEST(R(2, 2) == Rtest(2, 2), tt::tolerance(tol)); } else { boost::property_tree::ptree solver_settings_database; DataAssimilator da(MPI_COMM_WORLD, MPI_COMM_WORLD, 0, solver_settings_database); dealii::SparsityPattern pattern(3, 3, 3); pattern.add(0, 0); pattern.add(1, 0); pattern.add(1, 1); pattern.add(0, 1); pattern.add(2, 2); pattern.compress(); dealii::SparseMatrix R(pattern); R.add(0, 0, 0.1); R.add(1, 0, 0.3); R.add(1, 1, 1.0); R.add(0, 1, 0.3); R.add(2, 2, 0.2); std::vector> data; dealii::Vector ensemble_member(3); for (unsigned int i = 0; i < 1000; ++i) { da.fill_noise_vector(ensemble_member, R, R_is_diagonal); data.push_back(ensemble_member); } dealii::FullMatrix Rtest = calc_sample_covariance_dense(data); double tol = 20.; // Loose 20% tolerance because this is a statistical check BOOST_TEST(R(0, 0) == Rtest(0, 0), tt::tolerance(tol)); BOOST_TEST(R(1, 0) == Rtest(1, 0), tt::tolerance(tol)); BOOST_TEST(R(1, 1) == Rtest(1, 1), tt::tolerance(tol)); BOOST_TEST(R(0, 1) == Rtest(0, 1), tt::tolerance(tol)); BOOST_TEST(R(2, 2) == Rtest(2, 2), tt::tolerance(tol)); } }; // namespace adamantine void test_update_ensemble() { // Create the DoF mapping MPI_Comm communicator = MPI_COMM_WORLD; boost::property_tree::ptree database; database.put("import_mesh", false); database.put("length", 1); database.put("length_divisions", 1); database.put("height", 1); database.put("height_divisions", 1); boost::optional units_optional_database; adamantine::Geometry<2> geometry(communicator, database, units_optional_database); dealii::parallel::distributed::Triangulation<2> const &tria = geometry.get_triangulation(); dealii::FE_Q<2> fe(1); dealii::DoFHandler<2> dof_handler(tria); dof_handler.distribute_dofs(fe); int sim_size = 4; int expt_size = 2; std::vector expt_vec(2); expt_vec[0] = 2.5; expt_vec[1] = 9.5; std::pair, std::vector> expt_to_dof_mapping; expt_to_dof_mapping.first.resize(2); expt_to_dof_mapping.second.resize(2); expt_to_dof_mapping.first[0] = 0; expt_to_dof_mapping.first[1] = 1; expt_to_dof_mapping.second[0] = 1; expt_to_dof_mapping.second[1] = 3; boost::property_tree::ptree solver_settings_database; DataAssimilator da(communicator, communicator, 0, solver_settings_database); da._sim_size = sim_size; da._parameter_size = 0; da._expt_size = expt_size; da._num_ensemble_members = 3; da.update_covariance_sparsity_pattern<2>(dof_handler, 0); da.update_dof_mapping<2>(expt_to_dof_mapping); // Create the simulation data std::vector> augmented_state_ensemble(3); augmented_state_ensemble[0].reinit(2); augmented_state_ensemble[0].block(0).reinit(4); augmented_state_ensemble[0].block(0)(0) = 1.0; augmented_state_ensemble[0].block(0)(1) = 3.0; augmented_state_ensemble[0].block(0)(2) = 6.0; augmented_state_ensemble[0].block(0)(3) = 9.0; augmented_state_ensemble[0].collect_sizes(); augmented_state_ensemble[1].reinit(2); augmented_state_ensemble[1].block(0).reinit(4); augmented_state_ensemble[1].block(0)(0) = 1.5; augmented_state_ensemble[1].block(0)(1) = 3.2; augmented_state_ensemble[1].block(0)(2) = 6.3; augmented_state_ensemble[1].block(0)(3) = 9.7; augmented_state_ensemble[1].collect_sizes(); augmented_state_ensemble[2].reinit(2); augmented_state_ensemble[2].block(0).reinit(4); augmented_state_ensemble[2].block(0)(0) = 1.1; augmented_state_ensemble[2].block(0)(1) = 3.1; augmented_state_ensemble[2].block(0)(2) = 6.1; augmented_state_ensemble[2].block(0)(3) = 9.1; augmented_state_ensemble[2].collect_sizes(); // Build the sparse experimental covariance matrix dealii::SparsityPattern pattern(expt_size, expt_size, 1); pattern.add(0, 0); pattern.add(1, 1); pattern.compress(); dealii::SparseMatrix R(pattern); R.add(0, 0, 0.002); R.add(1, 1, 0.001); // Save the data at the observation points before assimilation std::vector sim_at_expt_pt_1_before(3); sim_at_expt_pt_1_before.push_back(augmented_state_ensemble[0].block(0)[1]); sim_at_expt_pt_1_before.push_back(augmented_state_ensemble[1].block(0)[1]); sim_at_expt_pt_1_before.push_back(augmented_state_ensemble[2].block(0)[1]); std::vector sim_at_expt_pt_2_before(3); sim_at_expt_pt_2_before.push_back(augmented_state_ensemble[0].block(0)[3]); sim_at_expt_pt_2_before.push_back(augmented_state_ensemble[1].block(0)[3]); sim_at_expt_pt_2_before.push_back(augmented_state_ensemble[2].block(0)[3]); // Update the simulation data da.update_ensemble(augmented_state_ensemble, expt_vec, R); // Save the data at the observation points after assimilation std::vector sim_at_expt_pt_1_after(3); sim_at_expt_pt_1_after.push_back(augmented_state_ensemble[0].block(0)[1]); sim_at_expt_pt_1_after.push_back(augmented_state_ensemble[1].block(0)[1]); sim_at_expt_pt_1_after.push_back(augmented_state_ensemble[2].block(0)[1]); std::vector sim_at_expt_pt_2_after(3); sim_at_expt_pt_2_after.push_back(augmented_state_ensemble[0].block(0)[3]); sim_at_expt_pt_2_after.push_back(augmented_state_ensemble[1].block(0)[3]); sim_at_expt_pt_2_after.push_back(augmented_state_ensemble[2].block(0)[3]); // Check the solution // The observed points should get closer to the experimental values // Large entries in R could make these fail spuriously for (int member = 0; member < 3; ++member) { BOOST_TEST(std::abs(expt_vec[0] - sim_at_expt_pt_1_after[member]) <= std::abs(expt_vec[0] - sim_at_expt_pt_1_before[member])); BOOST_TEST(std::abs(expt_vec[1] - sim_at_expt_pt_2_after[member]) <= std::abs(expt_vec[1] - sim_at_expt_pt_2_before[member])); } }; void test_update_ensemble_augmented() { // Create the DoF mapping MPI_Comm communicator = MPI_COMM_WORLD; boost::property_tree::ptree database; database.put("import_mesh", false); database.put("length", 1); database.put("length_divisions", 1); database.put("height", 1); database.put("height_divisions", 1); boost::optional units_optional_database; adamantine::Geometry<2> geometry(communicator, database, units_optional_database); dealii::parallel::distributed::Triangulation<2> const &tria = geometry.get_triangulation(); dealii::FE_Q<2> fe(1); dealii::DoFHandler<2> dof_handler(tria); dof_handler.distribute_dofs(fe); int sim_size = 4; int parameter_size = 2; int expt_size = 2; std::vector expt_vec(2); expt_vec[0] = 2.5; expt_vec[1] = 9.5; std::pair, std::vector> expt_to_dof_mapping; expt_to_dof_mapping.first.resize(2); expt_to_dof_mapping.second.resize(2); expt_to_dof_mapping.first[0] = 0; expt_to_dof_mapping.first[1] = 1; expt_to_dof_mapping.second[0] = 1; expt_to_dof_mapping.second[1] = 3; boost::property_tree::ptree solver_settings_database; DataAssimilator da(communicator, communicator, 0, solver_settings_database); da._sim_size = sim_size; da._parameter_size = parameter_size; da._expt_size = expt_size; da._num_ensemble_members = 3; da.update_covariance_sparsity_pattern<2>(dof_handler, parameter_size); da.update_dof_mapping<2>(expt_to_dof_mapping); // Create the simulation data std::vector> augmented_state_ensemble(3); augmented_state_ensemble[0].reinit(2); augmented_state_ensemble[0].block(0).reinit(sim_size); augmented_state_ensemble[0].block(0)(0) = 1.0; augmented_state_ensemble[0].block(0)(1) = 3.0; augmented_state_ensemble[0].block(0)(2) = 6.0; augmented_state_ensemble[0].block(0)(3) = 9.0; augmented_state_ensemble[1].reinit(2); augmented_state_ensemble[1].block(0).reinit(sim_size); augmented_state_ensemble[1].block(0)(0) = 1.5; augmented_state_ensemble[1].block(0)(1) = 3.2; augmented_state_ensemble[1].block(0)(2) = 6.3; augmented_state_ensemble[1].block(0)(3) = 9.7; augmented_state_ensemble[2].reinit(2); augmented_state_ensemble[2].block(0).reinit(sim_size); augmented_state_ensemble[2].block(0)(0) = 1.1; augmented_state_ensemble[2].block(0)(1) = 3.1; augmented_state_ensemble[2].block(0)(2) = 6.1; augmented_state_ensemble[2].block(0)(3) = 9.1; augmented_state_ensemble[0].block(1).reinit(parameter_size); augmented_state_ensemble[0].block(1)(0) = 1.0; augmented_state_ensemble[0].block(1)(0) = 5.0; augmented_state_ensemble[1].block(1).reinit(parameter_size); augmented_state_ensemble[1].block(1)(0) = 1.2; augmented_state_ensemble[1].block(1)(0) = 4.5; augmented_state_ensemble[2].block(1).reinit(parameter_size); augmented_state_ensemble[2].block(1)(0) = 1.4; augmented_state_ensemble[2].block(1)(0) = 5.5; augmented_state_ensemble[0].collect_sizes(); augmented_state_ensemble[1].collect_sizes(); augmented_state_ensemble[2].collect_sizes(); // Build the sparse experimental covariance matrix dealii::SparsityPattern pattern(expt_size, expt_size, 1); pattern.add(0, 0); pattern.add(1, 1); pattern.compress(); dealii::SparseMatrix R(pattern); R.add(0, 0, 0.002); R.add(1, 1, 0.001); // Save the data at the observation points before assimilation std::vector sim_at_expt_pt_1_before(3); sim_at_expt_pt_1_before.push_back(augmented_state_ensemble[0].block(0)[1]); sim_at_expt_pt_1_before.push_back(augmented_state_ensemble[1].block(0)[1]); sim_at_expt_pt_1_before.push_back(augmented_state_ensemble[2].block(0)[1]); std::vector sim_at_expt_pt_2_before(3); sim_at_expt_pt_2_before.push_back(augmented_state_ensemble[0].block(0)[3]); sim_at_expt_pt_2_before.push_back(augmented_state_ensemble[1].block(0)[3]); sim_at_expt_pt_2_before.push_back(augmented_state_ensemble[2].block(0)[3]); // Update the simulation data da.update_ensemble(augmented_state_ensemble, expt_vec, R); // Save the data at the observation points after assimilation std::vector sim_at_expt_pt_1_after(3); sim_at_expt_pt_1_after.push_back(augmented_state_ensemble[0].block(0)[1]); sim_at_expt_pt_1_after.push_back(augmented_state_ensemble[1].block(0)[1]); sim_at_expt_pt_1_after.push_back(augmented_state_ensemble[2].block(0)[1]); std::vector sim_at_expt_pt_2_after(3); sim_at_expt_pt_2_after.push_back(augmented_state_ensemble[0].block(0)[3]); sim_at_expt_pt_2_after.push_back(augmented_state_ensemble[1].block(0)[3]); sim_at_expt_pt_2_after.push_back(augmented_state_ensemble[2].block(0)[3]); // Check the solution // The observed points should get closer to the experimental values // Large entries in R could make these fail spuriously for (int member = 0; member < 3; ++member) { BOOST_TEST(std::abs(expt_vec[0] - sim_at_expt_pt_1_after[member]) <= std::abs(expt_vec[0] - sim_at_expt_pt_1_before[member])); BOOST_TEST(std::abs(expt_vec[1] - sim_at_expt_pt_2_after[member]) <= std::abs(expt_vec[1] - sim_at_expt_pt_2_before[member])); } }; private: template dealii::FullMatrix calc_sample_covariance_dense(std::vector vec_ensemble) const { unsigned int num_ensemble_members = vec_ensemble.size(); unsigned int vec_size = 0; if (vec_ensemble.size() > 0) { vec_size = vec_ensemble[0].size(); } // Calculate the mean dealii::Vector mean(vec_size); for (unsigned int i = 0; i < vec_size; ++i) { double sum = 0.0; for (unsigned int sample = 0; sample < num_ensemble_members; ++sample) { sum += vec_ensemble[sample][i]; } mean[i] = sum / num_ensemble_members; } // Calculate the anomaly dealii::FullMatrix anomaly(vec_size, num_ensemble_members); for (unsigned int member = 0; member < num_ensemble_members; ++member) { for (unsigned int i = 0; i < vec_size; ++i) { anomaly(i, member) = (vec_ensemble[member][i] - mean[i]) / std::sqrt(num_ensemble_members - 1.0); } } // FIXME This can be a problem for even moderately sized meshes dealii::FullMatrix cov(vec_size); anomaly.mTmult(cov, anomaly); return cov; } }; BOOST_AUTO_TEST_CASE(data_assimilator) { DataAssimilatorTester dat; dat.test_constructor(); dat.test_update_dof_mapping(); dat.test_update_covariance_sparsity_pattern(); dat.test_calc_sample_covariance_sparse(); dat.test_fill_noise_vector(true); dat.test_fill_noise_vector(false); dat.test_calc_H(); dat.test_calc_Hx(); dat.test_calc_kalman_gain(); dat.test_update_ensemble(); dat.test_update_ensemble_augmented(); } } // namespace adamantine