Program Listing for File euler_sieve_mult.hpp

Return to documentation for file (include/primes/euler_sieve_mult.hpp)

#pragma once

#include <algorithm>
#include <concepts>
#include <forward_list>
#include <type_traits>
#include <vector>

#include "../../external/gcem/include/gcem.hpp"
#include "primes_interface.hpp"

using std::forward_list;
using std::integral;
using std::signed_integral;
using std::unsigned_integral;
using std::vector;

namespace madlib {

// Member declarations

class euler_sieve_mult : public primes_interface<euler_sieve_mult> {

  // Used to tell in constant time whether a given number is prime or not.
  vector<bool> odd_sieve;

  // Used to access calculated primes in constant time.
  vector<prime_type> ordered_primes;

public:
  PRIMES_CONSTEXPR euler_sieve_mult() = default;

  explicit PRIMES_CONSTEXPR
  euler_sieve_mult(const unsigned_integral auto &initially_through);

  PRIMES_CONSTEXPR euler_sieve_mult &
  calculate_primes_through_impl(const unsigned_integral auto &through_value);

  PRIMES_CONSTEXPR bool is_prime_impl(const unsigned_integral auto &number);

  PRIMES_CONSTEXPR prime_type
  get_nth_prime_impl(const unsigned_integral auto &n);

  PRIMES_CONSTEXPR size_t number_of_mapped_primes_impl();

  PRIMES_CONSTEXPR prime_type highest_odd_number_mapped_impl();

  PRIMES_CONSTEXPR void auto_expand_mapped_primes_impl();

  PRIMES_CONSTEXPR void map_next_prime_impl();
};

// Some day when containers receive static trait checking, add that here.

// member definitions

inline PRIMES_CONSTEXPR euler_sieve_mult::euler_sieve_mult(
    const unsigned_integral auto &initially_through) {
  if (initially_through == 0) {
    return;
  }

  calculate_primes_through_impl(initially_through);
}

inline PRIMES_CONSTEXPR euler_sieve_mult &
euler_sieve_mult::calculate_primes_through_impl(
    const unsigned_integral auto &through_value) {
  if (through_value < odd_sieve.size()) {
    return *this;
  }

  // Call this close enough for reserving space.  Let the OS take on the virtual
  // memory optimizations.
  ordered_primes.reserve(static_cast<size_t>(
      static_cast<double>(through_value) / gcem::log2(through_value)));

  // prime datastructures
  if (ordered_primes.empty()) {
    ordered_primes.push_back(2);
    ordered_primes.push_back(3);
    odd_sieve.push_back(false);
    odd_sieve.push_back(true);
    if (through_value < odd_sieve.size()) {
      return *this;
    }
  }

  const size_t ordered_primes_initial_size = ordered_primes.size();
  { // Keep scope of euler_sieve only to where it is used.

    // Memory optimization
    // if 20, then checks through 19.  if 19, then checks through 19. 19 maps to
    // 10 stored odds, with a max index of 9.
    const size_t forward_list_size =
        static_cast<size_t>(
            gcem::ceil(static_cast<double>(through_value) / 2.0)) -
        odd_sieve.size();
    forward_list<prime_type> euler_sieve(forward_list_size);

    // Euler sieve
    generate(euler_sieve.begin(), euler_sieve.end(),
             [current_odd = odd_sieve.size() * 2 - 1]() mutable {
               current_odd += 2;
               return current_odd;
             });

    for (size_t i = 1; i * i <= through_value; ++i) {
      const size_t a_prime = ordered_primes.at(i);
      erase_if(euler_sieve,
               [a_prime](size_t num) { return num % a_prime == 0; });
      while (!euler_sieve.empty() && euler_sieve.front() < a_prime * a_prime) {
        ordered_primes.push_back(euler_sieve.front());
        euler_sieve.pop_front();
      }
      if (euler_sieve.empty()) {
        break;
      }
    }
  }

  // Adapt to bool vector for O(1) prime checking of precalculated values.
  odd_sieve.reserve(through_value * 2 + 1);

  for (auto new_prime_itr = ordered_primes.begin() +
                            static_cast<long>(ordered_primes_initial_size);
       new_prime_itr != ordered_primes.end(); ++new_prime_itr) {
    while (odd_sieve.size() * 2 + 1 < *new_prime_itr) {
      odd_sieve.push_back(false);
    }
    odd_sieve.push_back(true);
  }

  while (odd_sieve.size() * 2 + 1 <= through_value) {
    odd_sieve.push_back(false);
  }

  return *this;
}

inline PRIMES_CONSTEXPR bool
euler_sieve_mult::is_prime_impl(const unsigned_integral auto &number) {
  if (number == 2) {
    return true;
  }
  if (number % 2 == 0) {
    return false;
  }

  const size_t idx = (number - 1) / 2;
  if (idx >= odd_sieve.size()) {
    calculate_primes_through_impl(number);
  }
  return odd_sieve.at(idx);
}

inline PRIMES_CONSTEXPR prime_type
euler_sieve_mult::get_nth_prime_impl(const unsigned_integral auto &n) {
  while (n >= ordered_primes.size()) {
    auto_expand_mapped_primes_impl();
  }
  return ordered_primes.at(n);
}

inline PRIMES_CONSTEXPR void
euler_sieve_mult::auto_expand_mapped_primes_impl() {
  // Tuning factor for expanding primes known.  Probably overkill, but primes
  // exhibit complex, not-smooth behavior so what looks like over-engineering
  // is required.
  const size_t expansion_constant = 10;
  const size_t number_of_primes = number_of_mapped_primes_impl();
  for (size_t expansion_factor =
           (highest_odd_number_mapped_impl() + 1) * expansion_constant;
       number_of_primes == number_of_mapped_primes_impl();
       expansion_factor *= expansion_constant) {
    calculate_primes_through_impl(odd_sieve.size() * expansion_factor);
  }
}

inline PRIMES_CONSTEXPR void euler_sieve_mult::map_next_prime_impl() {
  const size_t number_of_primes = number_of_mapped_primes_impl();
  for (size_t map_through_number = highest_odd_number_mapped_impl() + 2;
       number_of_primes == ordered_primes.size(); map_through_number += 2) {
    calculate_primes_through_impl(map_through_number);
  }
}

inline PRIMES_CONSTEXPR size_t
euler_sieve_mult::number_of_mapped_primes_impl() {
  return ordered_primes.size() + 1; // 2 is hardcoded
}

inline PRIMES_CONSTEXPR prime_type
euler_sieve_mult::highest_odd_number_mapped_impl() {
  return odd_sieve.size() * 2 + 1;
}

} // namespace madlib