Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
DLR-AMR
GitHub Repository: DLR-AMR/t8code
Path: blob/main/test/t8_cmesh/t8_gtest_compute_first_element.cxx
914 views
/*
  This file is part of t8code.
  t8code is a C library to manage a collection (a forest) of multiple
  connected adaptive space-trees of general element classes in parallel.

  Copyright (C) 2025 the developers

  t8code is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  t8code is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with t8code; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/**
 * \file t8_gtest_compute_first_element.cxx
 * Test the computation of the first element of a tree. It is computed via mpirank * num_elems / mpisize.
 * We assume: 
 * - mpisize > 0
 * - mpirank >= 0
 * - num_elems >= 0
 * - mpirank <= mpisize
 * - num_elems <= 2^64-1
 * - mpisize <= 2^32-1
 * 
 * The tests check the correctness for small and large numbers.
 */

#include <gtest/gtest.h>
#include <limits>
#include <cmath>
#include <t8_cmesh/t8_cmesh.hxx>

struct t8_gtest_rank_times_global_num_elems_over_size: public testing::TestWithParam<std::tuple<int, int, int>>
{
 protected:
  void
  SetUp () override
  {
    rank_growth = std::get<0> (GetParam ());
    elem_growth = std::get<1> (GetParam ());
    size_growth = std::get<2> (GetParam ());
    rank_iter = log (std::numeric_limits<uint32_t>::max ()) / log (rank_growth);
    elem_iter = log (std::numeric_limits<uint64_t>::max ()) / log (elem_growth);
    size_iter = log (std::numeric_limits<uint32_t>::max ()) / log (size_growth);
  }
  void
  TearDown () override
  {
    /* nothing to do */
  }
  uint32_t rank_growth;
  uint32_t elem_growth;
  uint32_t size_growth;
#if T8_TEST_LEVEL_INT == 0
  const uint32_t max_iter = 100;
#elif T8_TEST_LEVEL_INT == 1
  const uint32_t max_iter = 50;
#else
  const uint32_t max_iter = 10;
#endif
  uint32_t rank_iter;
  uint32_t elem_iter;
  uint32_t size_iter;
};

TEST_P (t8_gtest_rank_times_global_num_elems_over_size, small_numbers)
{
  uint64_t num_elems = 1;
  for (uint32_t ielem = 1; ielem < max_iter; ++ielem) {
    num_elems += elem_growth;
    uint32_t size = 1;
    for (uint32_t isize = 1; isize < max_iter; ++isize) {
      size += size_growth;
      uint32_t rank = 1;
      /* Enforce rank <= size */
      for (uint32_t irank = 1; irank * rank_growth < size && irank < max_iter; ++irank) {
        rank += rank_growth;
        /* We only test for small numbers (much smaller that 2^64-1 here). Therefore this computation
         * will not overflow. */
        const uint64_t check_result = rank * num_elems / size;
        const uint64_t computed_result = t8_cmesh_get_first_element_of_process (rank, size, num_elems);
        EXPECT_EQ (check_result, computed_result)
          << "rank: " << rank << " num_elems: " << num_elems << " size: " << size;
      }
    }
  }
}

TEST_P (t8_gtest_rank_times_global_num_elems_over_size, large_numbers)
{
  /** 
   * We test the formula rank * num_elems / size for large numbers.
   * The formula is used in the t8code library to compute the number of elements
   * that are owned by a rank.
   * 
   * We use a recursive approach to compute the result.The number of ranks and elements growth exponentially to 
   * quickly reach the maximum values of uint32_t and uint64_t. We grow the number of elements in the outer loop 
   * and the number of ranks in the inner loop. The rate of growth is determined by the input of the test. 
   * Let n be the index of the outer loop and m the index of the inner loop.
   * 
   * The result in the innermost loop is computed by:
   * check_result(n, m) = num_elems * rank / size = elem_growth^n * rank_growth^m / size
   * 
   * To prevent overflow we use a recursive approach. Both in the inner and outer loop we use the following
   * formula to compute the control result based on the previous result:
   * floor (A^n*B/C) = A * floor (A^(n-1)*B/C) + floor (A * ((A^(n-1)B) % C) / C)
   * and + (A % C)*((A^(n-1)B) % C) / C is used to compute the remainder of the next step. 
   * for the inner and outer loop.
   * We do this trick to compute the result with respect to ielem in the outer loop, 
   * but also to compute the result with respect to irank in the inner loop.
   * 
   * 
   * check_result is the result with respect to ielem and irank.
   * check_result_elem is the result with respect to ielem.
   * 
   * check_result_elem is used as a starting point for check_result in the inner loop.
   * check_result_elem_(n-1) ^= floor(A^(n-1)*B/C)
   * elem_growth ^= A
   * size ^= C
   * check_result_elem_remain_(n-1) ^= (A * A^(n-1)B) % C)
   * check_result_elem = check_result_elem * elem_growth + elem_growth * check_result_elem_remain / size
   * check_result_elem_remain = (elem_growth * check_result_elem_remain) % size
   * 
   * For the inner loop the roles of A stays fixed and B is multiplied by rank_growth.
   * 
   * We use integer division, therefore we store the remainder of each update to 
   * prevent rounding errors.
  */
  uint64_t size = 1;
  for (uint32_t isize = 1; isize < size_iter; ++isize) {
    /* The very first result is 1 * 1 / size */
    uint64_t check_result_elem = 1 / size;
    /* The remainder of the element update */
    uint64_t check_result_elem_remain = 1;

    uint64_t num_elems = 1;
    /* Initialize factors */
    for (uint32_t ielem = 1; ielem < elem_iter; ++ielem) {
      uint32_t rank = 1;

      /** Used to compute elem^n * rank^m / size, where n is fixed. */
      uint64_t check_result = check_result_elem;
      /* The remainder of the rank update */
      uint64_t rank_remainder = check_result_elem_remain;
      for (uint32_t irank = 1; irank < rank_iter && rank <= size; ++irank) {
        const uint64_t computed_result = t8_cmesh_get_first_element_of_process (rank, size, num_elems);
        check_result = (rank == size) ? num_elems : check_result;

        ASSERT_EQ (computed_result, check_result)
          << "rank: " << rank << " num_elems: " << num_elems << " size: " << size;

        /* Update the result with respect to the updated rank */
        check_result *= rank_growth;
        check_result += rank_growth * rank_remainder / size;
        rank_remainder = (rank_growth * rank_remainder) % size;
        /* Update the rank */
        rank *= rank_growth;
      }
      /* Update the result with respect to the updated number of elements. */
      check_result_elem *= elem_growth;
      check_result_elem += elem_growth * check_result_elem_remain / size;
      check_result_elem_remain = (elem_growth * check_result_elem_remain) % size;
      /* Update the number of elements */
      num_elems *= elem_growth;
    }
    /* Update mpisize */
    size *= size_growth;
  }
}

INSTANTIATE_TEST_SUITE_P (t8_gtest_rank_times_global_num_elems_over_size,
                          t8_gtest_rank_times_global_num_elems_over_size,
                          testing::Combine (testing::Range (1, 10), testing::Range (1, 10), testing::Range (1, 10)));