fb.hpp
/*****************************************************************
* Functions for the factor base *
*****************************************************************/
#ifndef FB_HPP_
#define FB_HPP_
#include "cado_config.h" // for HAVE_GLIBC_VECTOR_INTERNALS
#include <cstddef> // for size_t, NULL
#include <cstdio> // for fprintf, FILE
#include <algorithm> // for sort, max
#include <array> // for array
#include <iosfwd> // for ostream
#include <limits> // for numeric_limits
#include <list> // for list
#include <map> // for map, operator!=, _Rb_tree_iter...
#include <mutex> // for lock_guard, mutex
#include <utility> // for pair
#include <vector> // for vector
#include "macros.h" // for ASSERT_ALWAYS, ASSERT, MAYBE_U...
#include "mpz_poly.h" // for cxx_mpz, cxx_mpz_poly
#include "cado_poly.h" // for MAX_DEGREE
#include "fb-types.h" // for fbprime_t, slice_index_t, fbro...
#include "las-base.hpp" // for _padded_pod
#include "las-config.h" // for FB_MAX_PARTS
#include "lock_guarded_container.hpp" // for lock_guarded_container
#include "mmap_allocator.hpp" // for mmap_allocator
#ifdef HAVE_GLIBC_VECTOR_INTERNALS
#include "mmappable_vector.hpp"
#else
/* yes, it's a hack */
#define mmappable_vector std::vector
#endif
#include "multityped_array.hpp" // for multityped_array_foreach, mult...
struct qlattice_basis;
struct cxx_param_list;
/* The *factor base* is made of *entries*. We have several vectors of
* entries, each with primes splitting in the same number of roots.
*
* For each set of *thresholds* and each *scale* value, we define a
* *slicing*. A slicing is a division of the factor base into *parts*.
* Then each part contains several vectors of *slices* (in the same
* manner as we have several vectors of entries in the factor base). A
* slice is formed by basically two pointers into the vector of entries
* in the factor base that have this number of roots.
*
* The factor base also contains auxiliary data attached to each vector
* of entries, namely the cumulative distribution function of the weight.
* This is used to subdivide slices into pieces of equal weight when
* needed.
*/
/* {{{ fb entries: sets of prime ideals above one single rational prime p. */
/* Forward declaration so fb_entry_general can use it in constructors */
template <int Nr_roots>
class fb_entry_x_roots;
/* A root modulo a prime power q. q is specified externally */
struct fb_general_root {
/* exp and oldexp are maximal such that:
If not projective and a == br (mod p^k), then p^exp | F(a,b)
-"- a == br (mod p^(k-1)), then p^oldexp | F(a,b)
If projective and ar == b -"- */
fbroot_t r;
bool proj;
unsigned char exp, oldexp;
fb_general_root (){}
fb_general_root (const fbroot_t r, const unsigned char nexp,
const unsigned char oldexp, const bool proj=false) :
r(r), proj(proj), exp(nexp), oldexp(oldexp) {}
fb_general_root (const fbroot_t r)
: r(r)
, proj(false)
, exp(1)
, oldexp(0) {}
/* Create a root from a linear polynomial */
static fb_general_root fb_linear_root (fbprime_t q, cxx_mpz_poly const & poly,
const unsigned char nexp, const unsigned char oldexp);
/* Constructor from the old format of storing projective roots, which has q
added to the root if the root is projective */
fb_general_root (const fb_root_p1 R,
const unsigned char nexp=1, const unsigned char oldexp=0) :
r(R.r), proj(R.proj),
exp(nexp), oldexp(oldexp) {}
/* A root is simple if it is not projective and the exp goes from 0 to 1 */
bool is_simple() const {return exp == 1 && oldexp == 0 && !proj;}
/* Convert a root to the old format of storing projective roots with q added */
unsigned long long to_old_format(const fbprime_t q) const {
return (unsigned long long) r + (proj ? q : 0);
}
/* Print one root. Projective roots are printed as r+q */
void fprint(FILE *out, const fbprime_t q) const {
fprintf(out, "%llu", to_old_format(q));
if (oldexp != 0 || this->exp != 1)
fprintf(out, ":%hhu:%hhu", oldexp, this->exp);
}
void transform(fb_general_root &result, const fbprime_t q,
const redc_invp_t invq,
const qlattice_basis &basis) const;
};
/* General entries are anything that needs auxiliary information:
Prime powers, projective roots, ramified primes where exp != oldexp + 1,
etc. They could, of course, also store the simple cases, but for those we
use the simple struct to conserve memory and to decide algorithms (batch
inversion, etc.) statically. */
class fb_entry_general : public _padded_pod<fb_entry_general> {
void read_roots (const char *, unsigned char, unsigned char, unsigned long);
public:
typedef fb_entry_general transformed_entry_t;
fbprime_t q, p; /* q = p^k */
redc_invp_t invq; /* invq = -1/q (mod 2^32), or (mod 2^64), depending on
the size of redc_invp_t */
fb_general_root roots[MAX_DEGREE];
unsigned char k, nr_roots;
/* Static class members to allow fb_vector<> to distinguish between and
operate on both kind of entries */
static const bool is_general_type = true;
static const unsigned char fixed_nr_roots = 0;
inline int get_nr_roots() const { return nr_roots; }
fb_entry_general() {}
template <int Nr_roots>
fb_entry_general (const fb_entry_x_roots<Nr_roots> &e);
fbprime_t get_q() const {return q;}
fbroot_t get_r(const size_t i) const {return roots[i].r;};
fbroot_t get_proj(const size_t i) const {return roots[i].proj;};
void parse_line (const char *line, unsigned long linenr);
void merge (const fb_entry_general &);
void fprint(FILE *out) const;
bool is_simple() const;
void transform_roots(transformed_entry_t &, const qlattice_basis &) const;
double weight() const {return 1./q * nr_roots;}
/* Allow sorting by p */
bool operator<(const fb_entry_general &other) const {return this->p < other.p;}
bool operator>(const fb_entry_general &other) const {return this->p > other.p;}
struct sort_byq {
bool operator()(fb_entry_general const & a, fb_entry_general const & b) const { return a.get_q() < b.get_q(); };
};
};
template <int Nr_roots>
class fb_transformed_entry_x_roots {
public:
fbprime_t p;
std::array<fbroot_t, Nr_roots> roots;
std::array<bool, Nr_roots> proj;
static const unsigned char k = 1, nr_roots = Nr_roots;
/* Static class members to allow fb_vector<> to distinguish between and
operate on both kind of entries */
static const bool is_general_type = false;
static const unsigned char fixed_nr_roots = Nr_roots;
fbprime_t get_q() const {return p;}
fbroot_t get_r(const size_t i) const {return roots[i];};
fbroot_t get_proj(const size_t i) const {return proj[i];};
};
/* "Simple" factor base entries. We imply q=p, k=1, oldexp=0, exp=1,
and projective=false for all roots. */
template <int Nr_roots>
class fb_entry_x_roots : public _padded_pod<fb_entry_x_roots<Nr_roots> > {
public:
typedef fb_transformed_entry_x_roots<Nr_roots> transformed_entry_t;
fbprime_t p;
redc_invp_t invq; /* invq = -1/q (mod 2^32), or (mod 2^64), depending on
the size of redc_invp_t */
std::array<fbroot_t, Nr_roots> roots;
/* Static class members to allow fb_vector<> to distinguish between and
operate on both kind of entries */
static const unsigned char k = 1, nr_roots = Nr_roots;
static const bool is_general_type = false;
static const unsigned char fixed_nr_roots = Nr_roots;
inline int get_nr_roots() const { return Nr_roots; }
// fb_entry_x_roots() {};
fb_entry_x_roots(fbprime_t p, redc_invp_t invq, fbroot_t * roots) : p(p), invq(invq) {
for (int i = 0; i < Nr_roots; i++)
this->roots[i] = roots[i];
}
/* Allow assignment-construction from general entries */
fb_entry_x_roots(const fb_entry_general &e) : p(e.p), invq(e.invq) {
ASSERT_ALWAYS(Nr_roots == e.nr_roots);
ASSERT(e.is_simple());
for (int i = 0; i < Nr_roots; i++)
roots[i] = e.roots[i].r;
}
fbprime_t get_q() const {return p;}
fbroot_t get_r(const size_t i) const {return roots[i];}
fbroot_t get_proj(const size_t i MAYBE_UNUSED) const {return false;}
double weight() const {return 1./p * Nr_roots;}
/* Allow sorting by p */
bool operator<(const fb_entry_x_roots<Nr_roots> &other) const {return this->p < other.p;}
bool operator>(const fb_entry_x_roots<Nr_roots> &other) const {return this->p > other.p;}
void fprint(FILE *) const;
void transform_roots(transformed_entry_t &, const qlattice_basis &) const;
};
/* }}} */
class fb_slice_interface {
public:
fb_slice_interface() = default;
virtual ~fb_slice_interface(){}
virtual size_t size() const = 0;
virtual unsigned char get_logp() const = 0;
virtual fbprime_t get_prime(slice_offset_t offset) const = 0;
virtual unsigned char get_k(slice_offset_t offset) const = 0;
/* global index across all fb parts */
virtual slice_index_t get_index() const = 0;
virtual double get_weight() const = 0;
virtual bool is_general() const = 0;
virtual int get_nr_roots() const = 0;
};
/* see fb_slice_weight.hpp */
template<typename FB_ENTRY_TYPE>
class fb_slice_weight_estimator;
template<typename FB_ENTRY_TYPE>
class fb_slice : public fb_slice_interface {
friend class fb_slice_weight_estimator<FB_ENTRY_TYPE>;
typedef mmappable_vector<FB_ENTRY_TYPE> fb_entry_vector;
typename fb_entry_vector::const_iterator _begin, _end;
unsigned char logp;
slice_index_t index; /* global index across all fb parts */
double weight;
friend struct helper_functor_subdivide_slices;
fb_slice(typename fb_entry_vector::const_iterator it, unsigned char logp) : _begin(it), _end(it), logp(logp), index(0), weight(0) {}
fb_slice(typename fb_entry_vector::const_iterator it, typename fb_entry_vector::const_iterator jt, unsigned char logp) : _begin(it), _end(jt), logp(logp), index(0), weight(0) {}
public:
typedef FB_ENTRY_TYPE entry_t;
inline typename fb_entry_vector::const_iterator begin() const { return _begin; }
inline typename fb_entry_vector::const_iterator end() const { return _end; }
inline size_t size() const override { return _end - _begin; }
unsigned char get_logp() const override { return logp; }
fbprime_t get_prime(slice_offset_t offset) const override {
/* While it may make sense to manipulate slices that exceed the
* expected max size during the work to construct them, it should
* be pretty clear that we'll never ever try to use get_prime,
* which is limited in its type, on a slice that has more prime
* ideals that this function can address */
ASSERT(size() <= std::numeric_limits<slice_offset_t>::max());
return _begin[offset].p;
}
unsigned char get_k(slice_offset_t offset) const override {
return _begin[offset].k;
/* power. Well, most often it's a constant ! We need to
* access it from the virtual base though. This is way we're
* not folding it to a template access. */
}
/* global index across all fb parts */
slice_index_t get_index() const override {return index;}
double get_weight() const override {return weight;}
bool is_general() const override { return FB_ENTRY_TYPE::is_general_type; }
/* get_nr_roots() on a fb_slice returns zero for slices of general
* type ! */
int get_nr_roots() const override { return FB_ENTRY_TYPE::fixed_nr_roots;}
};
/* entries and general_entries: we declare general entries, as well
* as entries for all numbers of roots between 1 and MAX_ROOTS
*
* (notationally, general_entries corresponds to -1 as a number of
* roots).
* */
template<typename T> struct entries_and_cdf {
typedef mmappable_vector<T> container_type;
typedef mmappable_vector<double> weight_container_type;
struct type : public container_type {
typedef typename entries_and_cdf<T>::container_type container_type;
typedef typename entries_and_cdf<T>::weight_container_type weight_container_type;
/* cumulative distribution function. This is set up by
* helper_functor_append. We use it to split into slices.
* weight_cdf[i] is \sum_{j < i} super[j].weight
*
* Thus we always need a lone 0 to initialize. See
* helper_functor_put_first_0, used in the fb_factorbase ctor.
*/
/* We *might* want to consider the cdf only for several entries
* at a time (say, 16), so as to minimize the cost of finding the
* split points */
weight_container_type weight_cdf;
inline weight_container_type::const_iterator weight_begin() const {
return weight_cdf.begin();
}
inline weight_container_type::const_iterator weight_end() const {
return weight_cdf.end();
}
inline weight_container_type::size_type weight_size() const {
return weight_cdf.size();
}
double weight_delta(size_t a, size_t b) const {
return weight_cdf[b] - weight_cdf[a];
}
double weight_delta(typename container_type::const_iterator a, typename container_type::const_iterator b) const {
return weight_cdf[b-container_type::begin()] - weight_cdf[a-container_type::begin()];
}
double weight_cdf_at(size_t a) const {
return weight_cdf[a];
}
double weight_cdf_at(typename container_type::const_iterator a) const {
return weight_cdf[a-container_type::begin()];
}
};
};
template<int n> struct fb_entries_factory {
typedef typename entries_and_cdf<fb_entry_x_roots<n>>::type type;
};
template<> struct fb_entries_factory<-1> {
typedef typename entries_and_cdf<fb_entry_general>::type type;
};
template<int n> struct fb_slices_factory {
typedef std::vector<fb_slice<fb_entry_x_roots<n>>> type;
};
template<> struct fb_slices_factory<-1> {
typedef std::vector<fb_slice<fb_entry_general>> type;
};
class fb_factorbase {
public:
/* Has to be <= MAX_DEGREE */
static const int MAX_ROOTS = MAX_DEGREE;
private:
cxx_mpz_poly f;
int side;
unsigned long lim;
unsigned long powlim;
public:
bool empty() const { return lim == 0; }
private:
typedef multityped_array<fb_entries_factory, -1, MAX_ROOTS+1> entries_t;
entries_t entries;
public:
typedef std::array<size_t, MAX_ROOTS+2> threshold_pos;
threshold_pos get_threshold_pos(fbprime_t) const;
private:
/* The threshold position cache is used to accelerate the creation of
* slicings. */
mutable std::map<fbprime_t, threshold_pos> threshold_pos_cache;
/* {{{ append. This inserts a pool of entries to the factor base. We
* do it with many entries in a row so as to avoid looping MAX_ROOTS
* times for each root, and so that we don't feel sorry to use
* multityped_array_foreach (even though in truth, it's quite
* probably fine)
*/
private:
struct helper_functor_append;
public:
void append(std::list<fb_entry_general> &pool);
/* }}} */
/* {{{ Various ways to count primes, prime ideals, and hit ratio
* ("weight") of entries in the whole factor base, or in subranges
*/
private:
struct helper_functor_count_primes;
struct helper_functor_count_prime_ideals;
struct helper_functor_count_weight;
struct helper_functor_count_combined;
struct helper_functor_count_primes_interval;
struct helper_functor_count_prime_ideals_interval;
struct helper_functor_count_weight_interval;
struct helper_functor_count_combined_interval;
public:
size_t count_primes() const;
size_t count_prime_ideals() const;
size_t count_weight() const;
/* }}} */
/* the key type lists the things with respect to which we're willing
* to divide the views on our factor base.
*/
struct key_type {
std::array<fbprime_t, FB_MAX_PARTS> thresholds;
fbprime_t td_thresh;
fbprime_t skipped;
double scale;
/* This might seem non obvious, but this parameters controls
* the size of the slices, because we want to enforce some
* relatively-even division. It's not entirely clear that we
* want it here, but we definitely want it somewhere. */
unsigned int nb_threads;
bool operator<(key_type const& x) const {
int r;
for(int i = 0; i < FB_MAX_PARTS; ++i) {
r = (thresholds[i] > x.thresholds[i]) - (x.thresholds[i] > thresholds[i]);
if (r) return r < 0;
}
r = (td_thresh > x.td_thresh) - (x.td_thresh > td_thresh);
if (r) return r < 0;
r = (skipped > x.skipped) - (x.skipped > skipped);
if (r) return r < 0;
return scale < x.scale;
}
};
public:
class slicing {
public:
struct stats_type {
/* explicit-initializing as below forces zero-initialization
* of members */
std::array<size_t, FB_MAX_PARTS> primes {{}};
std::array<size_t, FB_MAX_PARTS> ideals {{}};
std::array<double, FB_MAX_PARTS> weight {{}};
};
stats_type stats;
class part {
/* slices and general_slices: actually general_slices is
* slices for number of roots -1, and that is
* always a vector of length 1. And we have slices (vectors
* of fb_slice objects) for all numbers of roots between 1 and
* MAX_ROOTS.
*/
typedef multityped_array<fb_slices_factory, -1, MAX_ROOTS+1> slices_t;
friend struct helper_functor_subdivide_slices;
slices_t slices;
slice_index_t first_slice_index = 0;
public:
template<int n>
typename fb_slices_factory<n>::type& get_slices_vector_for_nroots() {
return slices.get<n>();
}
private:
/* the general vector (the one with index -1 in the
* multityped array) is made of "anything that is not
* simple" (as per the logic that used to be in
* fb_part::append and fb_entry_general::is_simple). That means:
* - all powers go to the general vector
* - everything below bkthresh, too (but see below).
* - primes with multiple roots as well (but to be honest,
* it's not entirely clear to me why -- after all, there's
* no special treatment to speak of).
*/
friend class slicing;
/* We use this to access our arrays called slices and
* general_slices.
*
* 0 <= i <slicesG.size() --> return &(slicesG[i])
* slicesG.size() <= i < slices0.size() --> return &(slices0[i-slicesG.size()])
* slices0.size() <= i < slices1.size() --> return &(slices1[i-slicesG.size()-slices0.size()])
* and so on.
*/
struct helper_functor_get {
typedef fb_slice_interface const * type;
typedef slice_index_t key_type;
template<typename T>
type operator()(T const & x, slice_index_t & k) {
if ((size_t) k < x.size()) {
return &(x[k]);
} else {
k -= x.size();
return NULL;
}
}
};
/* index is the global index across all fb parts */
fb_slice_interface const * get(slice_index_t index) const {
/* used to be in fb_vector::get_slice
*
* and in fb_part::get_slice for the lookup of the
* vector.
*
* TODO: dichotomy, perhaps ? Can we do the dichotomy
* elegantly ?
*/
if (index < first_slice_index) return NULL;
slice_index_t idx = index - first_slice_index;
if (idx >= nslices()) {
// index = idx - nslices();
return NULL;
}
const fb_slice_interface * res = multityped_array_locate<helper_functor_get>()(slices, idx);
ASSERT_ALWAYS(res);
ASSERT_ALWAYS(res->get_index() == index);
return res;
}
/* {{{ use caching for the number of slices, as it's a handy
* thing to query */
mutable slice_index_t _nslices = std::numeric_limits<slice_index_t>::max();
struct helper_functor_nslices {
template<typename T>
slice_index_t operator()(slice_index_t t, T const & x) const {
return t + x.size();
}
};
public:
slice_index_t nslices() const {
if (_nslices != std::numeric_limits<slice_index_t>::max())
return _nslices;
helper_functor_nslices N;
return _nslices = multityped_array_fold(N, 0, slices);
}
/* }}} */
private:
template<typename F>
struct foreach_slice_s {
F & f;
template<typename T>
void operator()(T & x) {
for(auto & a : x)
f(a);
}
template<typename T>
void operator()(T const & x) {
for(auto const & a : x)
f(a);
}
};
public:
template<typename F>
void foreach_slice(F & f) {
foreach_slice_s<F> FF { f };
multityped_array_foreach(FF, slices);
}
template<typename F>
void foreach_slice(F & f) const {
foreach_slice_s<F> FF { f };
multityped_array_foreach(FF, slices);
}
/*
* old g++ seems to have difficulties with this variant, and
* is puzzled by the apparent ambiguity -- newer g++ groks it
* correctly, as does clang
template<typename F>
void foreach_slice(F && f) {
multityped_array_foreach(foreach_slice_s<F> { f }, slices);
}
template<typename F>
void foreach_slice(F && f) const {
multityped_array_foreach(foreach_slice_s<F> { f }, slices);
}
*/
/* index: global index across all fb parts */
fb_slice_interface const & operator[](slice_index_t index) const {
/* This bombs out at runtime if get returns NULL, but
* then it should be an indication of a programmer
* mistake.
*/
return *get(index);
}
};
private:
/* We have "parts" that correspond to the various layers of the
* bucket sieving
*
* ==> THERE IS NO "part" FOR THE SMALL SIEVE <==
*
* This means that parts[0] will always be a meaningless
* placeholder. Indeed, the small sieve does not care about
* slices!
*/
std::array<part, FB_MAX_PARTS> parts;
/* toplevel is set by the ctor */
int toplevel = 0;
/* index: global index across all fb parts */
fb_slice_interface const * get(slice_index_t index) const {
for (auto const & p : parts) {
if (index < p.first_slice_index + p.nslices())
return p.get(index);
}
return NULL;
}
public:
part const & get_part(int i) const { return parts[i]; }
inline int get_toplevel() const { return toplevel; }
/* This accesses the *fb_slice* with this index. Not the vector of
* slices !
* index: global index across all fb parts */
fb_slice_interface const & operator[](slice_index_t index) const {
return *get(index);
}
public:
/* Here, when computing the slices, we stow away the small
* primes, and arrange them according to the internal logic of
* the small sieve. In particular, we want to keep track of where
* the resieved primes are.
*
* Note that the data that we prepare here is still not attached
* to any special-q. We're replacing what used to be done in
* init_fb_smallsieved, but not in small_sieve_init.
*
* Primes below the bucket-sieve threshold are small-sieved. Some
* will be treated specially when cofactoring:
*
* - primes such that nb_roots / p >= 1/tdhresh will be
* trial-divided.
* - primes (ideals) that are not trial divided and that are not
* powers are resieved.
*
* And of course, depending on the special-q, small-sieved prime
* ideals become projective, and therefore escape the general
* treatment.
*
* Note that the condition on trial division is not clear-cut
* w.r.t p. The small sieve code is somewhat dependent on the
* number of hits per row, which is I/p. And it matters to have
* small-sieved primes sorted according to this value. Hence, as
* p increases, we might have:
* small p's that are trial-divided because 1/p >=
* 1/td_thresh.
* some p's with p<=2*td_thresh, among which some are trial
* divided if 2 roots or more are above, otherwise they're
* resieved.
* some p's with p<=3*td_thresh, again with a distinction...
* and so on up to p>d*tdshresh, (d being the polynomial
* degree). Above this bound we're sure that we no longer see
* any trial-divided prime.
*
* For each slicing, we elect to compute two copies of the lists
* of prime ideals below bkthresh:
* - first, prime ideals that we know will be resieved. Those
* form the largest part of the list.
* - second, prime ideals above primes that will be trial-divided.
*
*/
/* This contains all factor base prime ideals below the bucket
* threshold. */
struct small_sieve_entries_t {
/* general ones. Not powers, not trial-divided ones. */
std::vector<fb_entry_general> resieved;
/* the rest. some are powers, others are trial-divided */
std::vector<fb_entry_general> rest;
/* from "rest" above, we can infer the list of trial-divided
* primes by merely restricting to entries with k==1 */
/* this is sort of a trash can. We won't use these primes for
* the small sieve, but they do matter for trial division */
std::vector<unsigned long> skipped;
};
small_sieve_entries_t small_sieve_entries;
/* TODO: I doubt that the struct above will stay. Seems awkward.
* We'd better have a single vector, and the position of the
* "end-of-resieved" thing.
*/
template<typename T>
void foreach_slice(T & f) {
for (auto & p : parts) {
p.foreach_slice(f);
}
}
template<typename T>
void foreach_slice(T && f) {
for (auto & p : parts) {
p.foreach_slice(f);
}
}
slicing() = default;
slicing(fb_factorbase const & fb, key_type const & K);
};
private:
lock_guarded_container<std::map<key_type, slicing>> cache;
int read(const char * const filename);
public:
/* accessors.
* As in the std::map case, the non-const accessor is allowed to
* create stuff. */
inline slicing & operator[](key_type const& K) {
std::lock_guard<std::mutex> foo(cache.mutex());
auto it = cache.find(K);
if (it != cache.end())
return it->second;
/* create a new slot */
slicing & res(cache[K] = slicing(*this, K));
return res;
}
inline slicing const & operator[](key_type const& K) const {
return cache.at(K);
}
private:
void make_linear();
void make_linear_threadpool (unsigned int nb_threads);
public:
/* Note that the intent here is to read the factor base once and
* for all. In the descent context, where we have several
* different fb lim parameters, we should get away with different
* slicings, instead of trying to redo the factor base
* initialization.
*/
fb_factorbase(cxx_cado_poly const & cpoly, int side, cxx_param_list & pl, const char * fbc_filename, int nthreads = 1);
fb_factorbase() = default;
fb_factorbase(fb_factorbase &&) = default;
fb_factorbase& operator=(fb_factorbase &&) = default;
private:
struct sorter {
template<typename T>
void operator()(T & x) {
/* not entirely clear to me. Shall we sort by q or by p ?
*/
typedef typename T::value_type X;
auto by_q = [](X const & a, X const & b) { return a.get_q() < b.get_q(); };
/*
auto by_p_then_q = [](X const & a, X const & b) {
return
a.get_p() < b.get_p() ||
a.get_p() == b.get_p() &&
a.get_q() < b.get_q();
};
*/
std::sort(x.begin(), x.end(), by_q);
}
};
public:
void finalize() {
sorter S;
multityped_array_foreach(S, entries);
}
};
std::ostream& operator<<(std::ostream& o, fb_factorbase::key_type const &);
unsigned char fb_log (double x, double y, double z);
unsigned char fb_log_delta (fbprime_t, unsigned long, unsigned long, double);
fbprime_t fb_is_power (fbprime_t, unsigned long *);
#endif /* FB_HPP_ */