Program Listing for File euler_sieve_sqrt.hpp

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

#pragma once

#include <algorithm>
#include <execution>
#include <type_traits>
#include <unordered_set>
#include <vector>

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

using std::integral;
using std::signed_integral;
using std::unsigned_integral;
using std::vector;
using std::execution::par_unseq;

namespace madlib {

// Member declarations

class euler_sieve_sqrt : public primes_interface<euler_sieve_sqrt> {

  // Used to tell in constant time whether a given number is prime or not.
  std::unordered_set<prime_type> odd_sieve;
  size_t highest_number_calculated_through = 3;

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

public:
  PRIMES_CONSTEXPR euler_sieve_sqrt();

  explicit PRIMES_CONSTEXPR
  euler_sieve_sqrt(const unsigned_integral auto &initially_through);

  PRIMES_CONSTEXPR euler_sieve_sqrt &
  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_sqrt::euler_sieve_sqrt() {
  odd_sieve.max_load_factor(0.5);
}

inline PRIMES_CONSTEXPR euler_sieve_sqrt::euler_sieve_sqrt(
    const unsigned_integral auto &initially_through) {
  odd_sieve.max_load_factor(0.5);
  if (initially_through == 0) {
    return;
  }

  calculate_primes_through_impl(initially_through);
}

inline PRIMES_CONSTEXPR euler_sieve_sqrt &
euler_sieve_sqrt::calculate_primes_through_impl(
    const unsigned_integral auto &through_value) {
  if (through_value < highest_number_calculated_through) {
    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);
  }

  { // 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 - highest_number_calculated_through)));
    vector<prime_type> euler_sieve;
    euler_sieve.reserve(forward_list_size);

    // Euler sieve
    for (auto current_odd = highest_number_calculated_through + 2;
         current_odd <= through_value; current_odd += 2) {
      if (std::all_of(par_unseq, ordered_primes.begin() + 1,
                      ordered_primes.end(), [current_odd](auto known_prime) {
                        return current_odd % known_prime != 0;
                      })) {
        euler_sieve.push_back(current_odd);
      }
    }
    euler_sieve.shrink_to_fit();

    while (!euler_sieve.empty()) {
      {
        auto itr = euler_sieve.begin();
        for (; itr != euler_sieve.end() &&
               *itr < ordered_primes.back() * ordered_primes.back();
             ++itr) {
          ordered_primes.push_back(*itr);
        }
        euler_sieve.erase(euler_sieve.begin(), itr);
      }

      if (euler_sieve.empty()) {
        break;
      }

      auto assign_itr = euler_sieve.begin();
      for (auto maybe_prime : euler_sieve) {
        if (std::all_of(
                par_unseq, ordered_primes.begin() + 1, ordered_primes.end(),
                [maybe_prime](auto known_prime) { return maybe_prime % known_prime != 0; })) {
          *assign_itr = maybe_prime;
          ++assign_itr;
        }
      }
      euler_sieve.erase(assign_itr, euler_sieve.end());
    }
  }

  highest_number_calculated_through = through_value;

  return *this;
}

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

  if (number >= highest_number_calculated_through) {
    calculate_primes_through_impl(number);
  }

  if(odd_sieve.size() < ordered_primes.size() && ordered_primes[odd_sieve.size()] <= number){
    odd_sieve.reserve(ordered_primes.size());

    for (auto prime_itr = ordered_primes.begin() + odd_sieve.size();
         prime_itr != ordered_primes.end(); ++prime_itr) {
      odd_sieve.insert(*prime_itr);
    }
  }
  return odd_sieve.contains(number);
}

inline PRIMES_CONSTEXPR prime_type
euler_sieve_sqrt::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_sqrt::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(highest_number_calculated_through *
                                  expansion_factor);
  }
}

inline PRIMES_CONSTEXPR void euler_sieve_sqrt::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_sqrt::number_of_mapped_primes_impl() {
  return ordered_primes.size() + 1; // 2 is hardcoded
}

inline PRIMES_CONSTEXPR prime_type
euler_sieve_sqrt::highest_odd_number_mapped_impl() {
  return highest_number_calculated_through;
}

} // namespace madlib