Revision d12d603036b334b53d6e886cfa985a082e981860 authored by Emmanuel Thomé on 29 January 2021, 06:20:31 UTC, committed by Emmanuel Thomé on 29 January 2021, 21:39:17 UTC
1 parent 590bfe4
Raw File
las-descent-trees.hpp
#ifndef LAS_DESCENT_DESCENT_TREES_HPP_
#define LAS_DESCENT_DESCENT_TREES_HPP_

#include <cstdio>             // for FILE
#include <cstdlib>            // for free
#include <algorithm>           // for max
#include <cmath>        /* isfinite is c99 and std::isfinite is c++11 ;
                         * it's not totally clear that #include <cmath> +
                         * accessing std::isfinite works.
                         */
#include <list>                // for list, operator!=, _List_iterator, list...
#include <mutex>               // for mutex, lock_guard
#include <set>                 // for operator!=, set, set<>::const_iterator
#include <sstream>             // for basic_ostream::operator<<, operator<<
#include <string>              // for string, allocator
#include <utility>             // for pair
#include <vector>              // for vector

#include <gmp.h>               // for mpz_srcptr, gmp_asprintf, mpz_sizeinbase

#include "las-todo-entry.hpp"  // for las_todo_entry
#include "macros.h"            // for ASSERT_ALWAYS
#include "relation.hpp"        // for relation_ab, relation, relation::pr
#include "timing.h"             // for seconds
#include "verbose.h"            // for verbose_output_print
#include "cxx_mpz.hpp"          // for cxx_mpz

#ifdef isfinite
/* Under some conditions, we can get #define'd C functions, which
 * obviously invalidate the C++ prototype (icc version 16.0.3 based on
 * gcc-6.1.0 on CentOS 7.2.1511)
 */
#undef isfinite
#endif

struct descent_tree {
    private:
        /* we have const members which need to lock the mutex */
        mutable std::mutex tree_lock;
    public:
    struct tree_label {
        int side = 0;
        relation::pr pr;
        tree_label() { }
        tree_label(int side, relation::pr const& pr ) : side(side), pr(pr) {}
        tree_label(int side, mpz_srcptr p, mpz_srcptr r) :side(side), pr(p, r) {}
        std::string operator()() const {
            std::ostringstream os;
            os << mpz_sizeinbase(pr.p, 2) << '@' << side;
            return os.str();
        }
        std::string fullname() const {
            char * str;
            gmp_asprintf(&str, "%d %Zd %Zd", side, (mpz_srcptr) pr.p, (mpz_srcptr) pr.r);
            std::string s = str;
            free(str);
            return s;
        }
        bool operator<(const tree_label& o) const {
            if (pr_cmp()(pr, o.pr)) return true;
            if (pr_cmp()(o.pr, pr)) return false;
            return side < o.side;
        }
    };
    /* For descent mode: we compute the expected time to finish given the
     * factor sizes, and deduce a deadline.  Assuming that not all
     * encountered factors are below the factor base bound, if we expect
     * an additional time T to finish the decomposition, we keep looking
     * for a better decomposition for a grace time which is computed as
     * x*T, for some configurable ratio x (one might think of x=0.2 for
     * instance. x is the grace_time_ratio member), which defines a
     * ``deadline'' for next step.  [If all factors happen to be smooth,
     * the deadline is immediate, of course.] If within the grace period,
     * a new relation is found, with an earlier implied deadline, the
     * deadline is updated. We say that the "decision is taken" when the
     * deadline passes, and the las machinery is told to decide that it
     * should proceed with the descent, and stop processing the current
     * special-q.
     */
    struct candidate_relation {
        relation rel;
        std::vector<std::pair<int, relation::pr> > outstanding;
        double time_left;
        double deadline;
        // bool marked_taken;      /* false until we take the decision */
        candidate_relation() : time_left(INFINITY), deadline(INFINITY) {} // , marked_taken(false) {}
        candidate_relation& operator=(candidate_relation const& o) {
            /* nothing very fancy, except that we keep the old deadline.
             * */
            rel = o.rel;
            outstanding = o.outstanding;
            time_left = o.time_left;
            if (o.deadline < deadline) deadline = o.deadline;
            return *this;
        }
        bool operator<(candidate_relation const& b) const
        {
            if (!rel) return false;
            if (!b.rel) return true;
            if (std::isfinite(time_left)) { return time_left < b.time_left; }
            return outstanding.size() < b.outstanding.size();
        }
        operator bool() const { return (bool) rel; }
        bool wins_the_game() const {
            return (bool) rel && (outstanding.empty() || seconds() >= deadline);
        }
        void set_time_left(double t, double grace_time_ratio) {
            time_left = t;
            if (outstanding.empty()) {
                deadline = seconds();
            } else {
                deadline = seconds() + grace_time_ratio * t;
            }
        }
    };
    struct tree {
        tree_label label;
        double spent;
        candidate_relation contender;
        std::list<tree *> children;
        int try_again;
        tree(tree_label const& label) : label(label), try_again(0) { }
        ~tree() {
            typedef std::list<tree *>::iterator it_t;
            for(it_t i = children.begin() ; i != children.end() ; i++)
                delete *i;
            children.clear();
        }
        bool is_successful() const {
            if (!contender.rel && !try_again)
                return false;
            typedef std::list<tree *>::const_iterator it_t;
            for(it_t i = children.begin() ; i != children.end() ; i++) {
                if (!(*i)->is_successful())
                    return false;
            }
            return true;
        }
    };

    /* A "descent tree" is really a set of descent trees, one for each
     * top-level special-q we've considered in the main loop in las. The
     * top-level roots are found in [forest].
     *
     * At a given point in the process, the list [current] contains
     * pointers to the different levels of the tree that form the lineage
     * of the last special-q being considered. More precisely,
     * current.back()->label is the special-q that is being considered,
     * current.back()->children() is empty, and
     * (*----current.end())->label is the one whose consideration led us
     * to consider it, and so on.
     *
     * It's admittedly dangerous to have pointers around, here.
     */
    std::list<tree *> forest;
    std::list<tree *> current;       /* stack of trees */

    /* This is an ugly temporary hack */
    typedef std::set<relation_ab> visited_t;
    visited_t visited;


    ~descent_tree() {
        typedef std::list<tree *>::iterator it_t;
        for(it_t i = forest.begin() ; i != forest.end() ; i++)
            delete *i;
        forest.clear();
    }

    /* designing a copy ctor for this structure would be tedious */
    descent_tree() = default;
    descent_tree(descent_tree const& t) = delete;

    void new_node(las_todo_entry const & doing) {
        std::lock_guard<std::mutex> lock(tree_lock);
        int level = doing.depth;
        ASSERT_ALWAYS(level == (int) current.size());
        tree * kid = new tree(tree_label(doing.side, doing.p, doing.r));
        kid->spent = -seconds();
        if (current.empty()) {
            forest.push_back(kid);
            current.push_back(kid);
        } else {
            current.back()->children.push_back(kid);
            current.push_back(kid);
        }
    }

    void ditch_node() {
        tree * kid = current.back();
        current.pop_back();
        if (!current.empty()) {
            ASSERT_ALWAYS(current.back()->children.back() == kid);
            current.back()->children.pop_back();
        } else {
            ASSERT_ALWAYS(forest.back() == kid);
            forest.pop_back();
        }
        delete kid;
    }
    void done_node() {
        current.back()->spent += seconds();
        current.pop_back();
    }

    candidate_relation const& current_best_candidate() const {
        /* outside multithreaded context */
        return current.back()->contender;
    }

    void mark_try_again(int i) {
        current.back()->try_again = i;
    }

    /* return true if the decision to go to the next step of the descent
     * should be taken now, and register this relation has having been
     * taken */
    bool must_take_decision() {
        std::lock_guard<std::mutex> lock(tree_lock);
        bool res = current.back()->contender.wins_the_game();
        return res;
    }

    void take_decision() {
        /* must be called outside multithreaded context */
        // current.back()->contender.marked_taken = true;
        relation_ab ab = current.back()->contender.rel;
        visited.insert(ab);
    }

    /* this returns true if the decision should be taken now */
    bool new_candidate_relation(candidate_relation& newcomer)
    {
        std::lock_guard<std::mutex> lock(tree_lock);
        candidate_relation & defender(current.back()->contender);
        if (newcomer < defender) {
            if (newcomer.outstanding.empty()) {
                verbose_output_print(0, 1, "# [descent] Yiippee, splitting done\n");
            } else if (std::isfinite(defender.deadline)) {
                /* This implies that newcomer.deadline is also finite */
                double delta = defender.time_left-newcomer.time_left;
                verbose_output_print(0, 1, "# [descent] Improved ETA by %.2f\n", delta);
            } else if (defender) {
                /* This implies that we have fewer outstanding
                 * special-q's */
                verbose_output_print(0, 1, "# [descent] Improved number of children to split from %u to %u\n",
                        (unsigned int) defender.outstanding.size(),
                        (unsigned int) newcomer.outstanding.size());
            }
            defender = newcomer;
            if (!defender.outstanding.empty()) {
                verbose_output_print(0, 1, "# [descent] still searching for %.2f\n", defender.deadline - seconds());
            }
        }
        bool res = defender.wins_the_game();
        return res;
    }

    bool must_avoid(relation const& rel) const {
        relation_ab ab = rel;
        std::lock_guard<std::mutex> lock(tree_lock);
        bool answer = visited.find(ab) != visited.end();
        return answer;
    }
    int depth() {
        return current.size();
    }
    bool is_successful(tree * t) {
        return t->is_successful();
    }
    int tree_depth(tree * t) {
        int d = 0;
        typedef std::list<tree *>::iterator it_t;
        for(it_t i = t->children.begin() ; i != t->children.end() ; i++) {
            d = std::max(d, 1 + tree_depth(*i));
        }
        return d;
    }
    int tree_weight(tree * t) {
        int w = 1;
        typedef std::list<tree *>::iterator it_t;
        for(it_t i = t->children.begin() ; i != t->children.end() ; i++) {
            w += tree_weight(*i);
        }
        return w;
    }
    int display_tree(FILE* o, tree * t, std::string const& prefix);
    void display_last_tree(FILE * o);

    void display_all_trees(FILE * o);
};
#endif	/* LAS_DESCENT_DESCENT_TREES_HPP_ */
back to top