From 1a08eca840a0fde90c3791efca4deefc0aa66916 Mon Sep 17 00:00:00 2001 From: Maximilien Colange Date: Fri, 25 Nov 2016 11:52:11 +0100 Subject: [PATCH] Add a new, parameterized, version of the Couvreur emptiness check. This version has optimization for explicit twa, and also for weak and terminal (depending on whether an accepting run is requested) automata. * spot/twaalgos/couvreurnew.hh, spot/twaalgos/couvreurnew.cc, spot/twaalgos/Makefile.am: New files for the new algorithm. * spot/twaalgos/emptiness.cc, tests/core/randtgba.cc: Register new algorithm. --- NEWS | 7 + spot/twaalgos/Makefile.am | 2 + spot/twaalgos/couvreurnew.cc | 864 +++++++++++++++++++++++++++++++++++ spot/twaalgos/couvreurnew.hh | 49 ++ spot/twaalgos/emptiness.cc | 3 + tests/core/randtgba.cc | 2 + 6 files changed, 927 insertions(+) create mode 100644 spot/twaalgos/couvreurnew.cc create mode 100644 spot/twaalgos/couvreurnew.hh diff --git a/NEWS b/NEWS index 8a5f5929e..12861d97e 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,13 @@ New in spot 2.2.1.dev (Not yet released) * A twa is required to have at least one state, the initial state. + * The Couvreur emptiness check has been rewritten to use the explicit + interface when possible, to avoid overkill memory allocations. + The new version has further optimizations for weak and terminal + automata. Overall, this new version is roughly 4x faster on explicit + automata than the former one. The old version has been kept for + compatibility. + Build: * If the system has an installed libltdl library, use it instead of diff --git a/spot/twaalgos/Makefile.am b/spot/twaalgos/Makefile.am index da694e374..5f7f17928 100644 --- a/spot/twaalgos/Makefile.am +++ b/spot/twaalgos/Makefile.am @@ -55,6 +55,7 @@ twaalgos_HEADERS = \ magic.hh \ mask.hh \ minimize.hh \ + couvreurnew.hh \ neverclaim.hh \ postproc.hh \ powerset.hh \ @@ -109,6 +110,7 @@ libtwaalgos_la_SOURCES = \ magic.cc \ mask.cc \ minimize.cc \ + couvreurnew.cc \ ndfs_result.hxx \ neverclaim.cc \ postproc.cc \ diff --git a/spot/twaalgos/couvreurnew.cc b/spot/twaalgos/couvreurnew.cc new file mode 100644 index 000000000..689d6c537 --- /dev/null +++ b/spot/twaalgos/couvreurnew.cc @@ -0,0 +1,864 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2016 Laboratoire de Recherche et Développement de l'Epita. +// +// This file is part of Spot, a model checking library. +// +// Spot 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 3 of the License, or +// (at your option) any later version. +// +// Spot 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 this program. If not, see . + +#include + +#include +#include +#include +#include +#include + +#include + +namespace spot +{ + namespace + { + using explicit_iterator = twa_graph::graph_t::const_iterator; + + // A proxy class that allows to manipulate an iterator from the + // explicit interface as an iterator from the abstract interface. + class explicitproxy + { + public: + explicitproxy(explicit_iterator it) + : it_(it) + {} + + const explicitproxy* + operator->() const + { + return this; + } + + explicitproxy* + operator->() + { + return this; + } + + bool + done() const + { + return !it_; + } + + unsigned + dst() const + { + return it_->dst; + } + + acc_cond::mark_t + acc() const + { + return it_->acc; + } + + void + next() + { + ++it_; + } + + private: + explicit_iterator it_; + }; + + template + class twa_iteration + { + }; + + template<> + class twa_iteration + { + public: + using state_t = const state*; + using iterator_t = twa_succ_iterator*; + template + using state_map = state_map; + + template + static + std::pair, bool> + h_emplace(state_map& h, state_t s, val i) + { + auto p = h.emplace(s, i); + return std::make_pair(*p.first, p.second); + } + + static + std::pair + h_find(const state_map& h, state_t s) + { + auto p = h.find(s); + if (p == h.end()) + return std::make_pair(nullptr, 0); + else + return std::make_pair(p->first, p->second); + } + + static + unsigned + h_count(const state_map& h, const std::function& p) + { + unsigned count = 0; + for (auto i : h) + if (p(i.second)) + ++count; + return count; + } + + static + state_t + initial_state(const const_twa_ptr& twa_p) + { + return twa_p->get_init_state(); + } + + static + iterator_t + succ(const const_twa_ptr& twa_p, state_t s) + { + auto res = twa_p->succ_iter(s); + res->first(); + return res; + } + + static + void + destroy(state_t s) + { + s->destroy(); + } + + static + const state* + to_state(const const_twa_ptr&, state_t s) + { + return s; + } + + static + state_t + from_state(const const_twa_ptr&, const state* s) + { + return s; + } + + static + void + it_destroy(const const_twa_ptr& twa_p, iterator_t it) + { + twa_p->release_iter(it); + } + }; + + template<> + class twa_iteration + { + public: + using state_t = unsigned; + using iterator_t = explicitproxy; + template + using state_map = std::vector; + + template + static + std::pair, bool> + h_emplace(state_map& h, state_t s, val i) + { + if (h[s] == val()) + { + h[s] = i; + return std::make_pair(std::make_pair(s, h[s]), true); + } + else + { + return std::make_pair(std::make_pair(s, h[s]), false); + } + } + + static + std::pair + h_find(const state_map& h, state_t s) + { + assert(s < h.size()); + return std::make_pair(s, h[s]); + } + + static + unsigned + h_count(const state_map& h, const std::function& p) + { + unsigned count = 0; + for (auto i : h) + if (p(i)) + ++count; + return count; + } + + static + state_t + initial_state(const const_twa_graph_ptr& twa_p) + { + return twa_p->get_init_state_number(); + } + + static + iterator_t + succ(const const_twa_graph_ptr& twa_p, state_t s) + { + return explicitproxy(twa_p->out(s).begin()); + } + + static + const state* + to_state(const const_twa_graph_ptr& twa_p, state_t s) + { + return twa_p->state_from_number(s); + } + + static + state_t + from_state(const const_twa_graph_ptr& twa_p, const state* s) + { + return twa_p->state_number(s); + } + + static + void + destroy(state_t) + { + } + + static + void + it_destroy(const const_twa_ptr&, iterator_t) + { + } + }; + + // A simple struct representing an SCC. + struct scc + { + scc(int i): index(i), condition(0U) {} + + int index; + acc_cond::mark_t condition; + }; + + template + using automaton_ptr = typename std::conditional::type; + + // The status of the emptiness-check on success. + // It contains everyting needed to build a counter-example: + // the automaton, the stack of SCCs traversed by the counter-example, + // and the heap of visited states with their indices. + template + struct couvreur99_new_status + { + using T = twa_iteration; + + couvreur99_new_status(const automaton_ptr& a) + : aut(a) + { + // Appropriate version is resolved through SFINAE. + init(); + } + + automaton_ptr aut; + std::stack root; + typename T::template state_map h; + typename T::state_t cycle_seed; + + private: + template + typename std::enable_if::type + init() + { + // Initialize h with the right size. + h.resize(aut->num_states(), 0); + } + + template + typename std::enable_if::type + init() + {} + }; + + template + using couvreur99_new_status_ptr = + std::shared_ptr>; + + template + class couvreur99_new_result final : + public emptiness_check_result, + public acss_statistics + { + using T = twa_iteration; + public: + couvreur99_new_result(const couvreur99_new_status_ptr& ecs) + : emptiness_check_result(ecs->aut), ecs_(ecs) + {} + + virtual + unsigned + acss_states() const override + { + int scc_root = ecs_->root.top().index; + return T::h_count(ecs_->h, + [scc_root](int s) { return s >= scc_root; }); + } + + twa_run_ptr + accepting_run() override; + private: + couvreur99_new_status_ptr ecs_; + twa_run_ptr run_; + + void + accepting_cycle() + { + acc_cond::mark_t acc_to_traverse = + ecs_->aut->acc().accepting_sets(ecs_->root.top().condition); + // Compute an accepting cycle using successive BFS that are + // restarted from the point reached after we have discovered a + // transition with a new acceptance condition. + // + // This idea is taken from Product::findWitness in LBTT 1.1.2, + // which in turn is probably inspired from + // @Article{latvala.00.fi, + // author = {Timo Latvala and Keijo Heljanko}, + // title = {Coping With Strong Fairness}, + // journal = {Fundamenta Informaticae}, + // year = {2000}, + // volume = {43}, + // number = {1--4}, + // pages = {1--19}, + // publisher = {IOS Press} + // } + const state* substart = T::to_state(ecs_->aut, ecs_->cycle_seed); + const state* cycle_seed = T::to_state(ecs_->aut, ecs_->cycle_seed); + do + { + struct scc_bfs final: bfs_steps + { + const couvreur99_new_status* ecs; + couvreur99_new_result* r; + acc_cond::mark_t& acc_to_traverse; + int scc_root; + + scc_bfs(const couvreur99_new_status* ecs, + couvreur99_new_result* r, + acc_cond::mark_t& acc_to_traverse) + : bfs_steps(ecs->aut), ecs(ecs), r(r) + , acc_to_traverse(acc_to_traverse) + , scc_root(ecs->root.top().index) + { + } + + virtual const state* + filter(const state* s) override + { + auto i = T::h_find(ecs->h, T::from_state(ecs->aut, s)); + s->destroy(); + // Ignore unknown states. + if (i.second == 0) + return nullptr; + // Stay in the final SCC. + if (i.second < scc_root) + return nullptr; + r->inc_ars_cycle_states(); + return T::to_state(ecs->aut, i.first); + } + + virtual bool + match(twa_run::step& st, const state* s) override + { + acc_cond::mark_t less_acc = + acc_to_traverse - st.acc; + if (less_acc != acc_to_traverse + || (acc_to_traverse == 0U + && T::from_state(ecs->aut, s) == ecs->cycle_seed)) + { + acc_to_traverse = less_acc; + return true; + } + return false; + } + } b(ecs_.get(), this, acc_to_traverse); + + substart = b.search(substart, run_->cycle); + assert(substart); + } + while (acc_to_traverse != 0U || substart != cycle_seed); + } + }; + + template + class shortest_path final : public bfs_steps + { + using T = twa_iteration; + public: + shortest_path(const state_set* t, + const couvreur99_new_status_ptr& ecs, + couvreur99_new_result* r) + : bfs_steps(ecs->aut), target(t), ecs(ecs), r(r) + { + } + + const state* + search(const state* start, twa_run::steps& l) + { + return this->bfs_steps::search(filter(start), l); + } + + const state* + filter(const state* s) override + { + r->inc_ars_prefix_states(); + auto i = T::h_find(ecs->h, T::from_state(ecs->aut, s)); + s->destroy(); + // Ignore unknown states ... + if (i.second == 0) + return nullptr; + // ... as well as dead states + if (i.second == -1) + return nullptr; + return T::to_state(ecs->aut, i.first); + } + + bool + match(twa_run::step&, const state* dest) override + { + return target->find(dest) != target->end(); + } + private: + state_set seen; + const state_set* target; + couvreur99_new_status_ptr ecs; + couvreur99_new_result* r; + }; + + template + twa_run_ptr + couvreur99_new_result::accepting_run() + { + run_ = std::make_shared(ecs_->aut); + + assert(!ecs_->root.empty()); + + // Compute an accepting cycle. + accepting_cycle(); + + // Compute the prefix: it is the shorted path from the initial + // state of the automaton to any state of the cycle. + + // Register all states from the cycle as targets of the BFS. + state_set ss; + for (twa_run::steps::const_iterator i = run_->cycle.begin(); + i != run_->cycle.end(); ++i) + ss.insert(i->s); + shortest_path shpath(&ss, ecs_, this); + + const state* prefix_start = + T::to_state(ecs_->aut, T::initial_state(ecs_->aut)); + // There are two cases: either the initial state is already in + // the cycle, or it is not. If it is, we will have to rotate + // the cycle so it begins at this position. Otherwise we will shift + // the cycle so it begins at the state that follows the prefix. + // cycle_entry_point is that state. + const state* cycle_entry_point; + state_set::const_iterator ps = ss.find(prefix_start); + if (ps != ss.end()) + { + // The initial state is in the cycle. + prefix_start->destroy(); + cycle_entry_point = *ps; + } + else + { + // This initial state is outside the cycle. Compute the prefix. + cycle_entry_point = shpath.search(prefix_start, run_->prefix); + } + + // Locate cycle_entry_point on the cycle. + twa_run::steps::iterator cycle_ep_it; + for (cycle_ep_it = run_->cycle.begin(); + cycle_ep_it != run_->cycle.end() + && cycle_entry_point->compare(cycle_ep_it->s); ++cycle_ep_it) + continue; + assert(cycle_ep_it != run_->cycle.end()); + + // Now shift the cycle so it starts on cycle_entry_point + run_->cycle.splice(run_->cycle.end(), run_->cycle, + run_->cycle.begin(), cycle_ep_it); + + return run_; + } + + // A simple enum for the different automata strengths. + enum twa_strength { STRONG, WEAK, TERMINAL }; + + template + class couvreur99_new : public emptiness_check, public ec_statistics + { + using T = twa_iteration; + using state_t = typename T::state_t; + using iterator_t = typename T::iterator_t; + using pair_state_iter = std::pair; + + couvreur99_new_status_ptr ecs_; + + public: + couvreur99_new(const automaton_ptr& a, + option_map o = option_map()) + : emptiness_check(a, o) + , ecs_(std::make_shared>(a)) + { + } + + virtual + emptiness_check_result_ptr + check() override + { + return check_impl(); + } + + // The following two methods anticipe the future interface of the + // class emptiness_check. Following the interface of twa, the class + // emptiness_check_result should not be exposed. + bool + is_empty() + { + return check_impl(); + } + + twa_run + accepting_run() + { + return check_impl()->accepting_run(); + } + +private: + // A union-like struct to store the result of an emptiness. + // If the caller only wants to test emptiness, it is sufficient to + // store the Boolean result. + // Otherwise, we need to store all the information needed to build + // an accepting run. + struct check_result + { + enum { BOOL, PTR } tag; + union + { + bool res; + emptiness_check_result_ptr ecr; + }; + + // Copy constructor. + check_result(const check_result& o) + : tag(o.tag) + { + if (tag == BOOL) + res = o.res; + else + ecr = o.ecr; + } + check_result(bool r) + : tag(BOOL), res(r) + { + } + check_result(std::nullptr_t) + : tag(PTR), ecr(nullptr) + { + } + check_result(const emptiness_check_result_ptr& p) + : tag(PTR), ecr(p) + { + } + // A destructor to properly dereference the shared_pointer. + ~check_result() + { + if (tag == PTR) + ecr.~emptiness_check_result_ptr(); + } + + // Handy cast operators. + // Note that a pointer can be cast to a Boolean as usual. + operator bool() const + { + if (tag == BOOL) + return res; + else + return ecr; + } + operator emptiness_check_result_ptr() const + { + if (tag == PTR) + return ecr; + else + throw std::runtime_error("accepting run was not requested"); + } + }; + + template + check_result + check_impl() + { + { + // check trivial acceptance condition + auto acc = ecs_->aut->acc(); + if (acc.is_f()) + return nullptr; + // check whether fin acceptance conditions are used + if (acc.uses_fin_acceptance()) + throw std::runtime_error + ("Fin acceptance is not supported by couvreur99()"); + } + + // We use five main data in this algorithm: + // * root, a stack of strongly connected components (SCC), + // * h, a hash of all visited nodes with their order, + // ("Hash" in Couvreur's paper) + // * arc, a stack of acceptance conditions between each of these SCC. + std::stack arc; + // * num, the number of visited nodes. Used to set the order of each + // visited node. + int num = 1; + // * todo, the depth-first-search stack. This holds pairs of the form + // (STATE, ITERATOR) where ITERATOR is a twa_succ_iterator over the + // successors of STATE. In our use, ITERATOR should always be freed + // when todo is popped, but STATE should not because it is also used + // as a key in h. + std::stack todo; + // * live, a stack of live nodes + std::deque live; + + // Setup depth-first search from the initial state. + { + state_t init = T::initial_state(ecs_->aut); + ecs_->h[init] = 1; + ecs_->root.push(1); + if (strength == STRONG) + arc.push(0U); + auto iter = T::succ(ecs_->aut, init); + todo.emplace(init, iter); + live.emplace_back(init); + inc_depth(); + } + + while (!todo.empty()) + { + if (strength == STRONG) + assert(ecs_->root.size() == arc.size()); + + // We are looking at the next successor in SUCC. + auto succ = todo.top().second; + + // If there is no more successors, backtrack. + if (succ->done()) + { + // We have explored all successors of state CURR. + state_t curr = todo.top().first; + + // Backtrack todo. + todo.pop(); + + // When backtracking the root of an SCC, we must also remove + // that SCC from the ARC/ROOT stacks. We must discard from H + // all reachable states from this SCC. + assert(!ecs_->root.empty()); + if (ecs_->root.top().index == ecs_->h[curr]) + { + if (strength == STRONG) + { + assert(!arc.empty()); + arc.pop(); + } + // Remove all elements of this SCC from the live stack. + auto i = std::find(live.rbegin(), live.rend(), curr); + assert(i != live.rend()); + ++i; // Because base() does -1 + for (auto it = i.base(); it != live.end(); ++it) + { + ecs_->h[*it] = -1; + } + live.erase(i.base(), live.end()); + ecs_->root.pop(); + } + T::it_destroy(ecs_->aut, succ); + // Do not destroy curr: it is a key in h. + continue; + } + + // We have a successor to look at. + inc_transitions(); + // Fetch the values we are interested in... + state_t dest = succ->dst(); + auto acc = succ->acc(); + if (!need_accepting_run) + if (strength == TERMINAL && ecs_->aut->acc().accepting(acc)) + { + // We have found an accepting SCC. + // Release all iterators in todo. + while (!todo.empty()) + { + T::it_destroy(ecs_->aut, todo.top().second); + todo.pop(); + dec_depth(); + } + // We do not need an accepting run. + return true; + } + // ... and point the iterator to the next successor, for + // the next iteration. + todo.top().second->next(); + + // We do not need succ from now on. + + // Are we going to a new state? + auto p = T::h_emplace(ecs_->h, dest, num+1); + if (p.second) + { + // Yes. Bump number, stack the stack, and register its + // successors for later processing. + ecs_->root.push(++num); + if (strength == STRONG) + arc.push(acc); + iterator_t iter = T::succ(ecs_->aut, dest); + todo.emplace(dest, iter); + live.emplace_back(dest); + inc_depth(); + continue; + } + T::destroy(dest); + + // If we have reached a dead component, ignore it. + if (p.first.second == -1) + continue; + + // Now this is the most interesting case. We have reach a + // state S1 which is already part of a non-dead SCC. Any such + // non-dead SCC has necessarily been crossed by our path to + // this state: there is a state S2 in our path which belongs + // to this SCC too. We are going to merge all states between + // this S1 and S2 into this SCC. + // + // This merge is easy to do because the order of the SCC in + // root is ascending: we just have to merge all SCCs from the + // top of root that have an index greater than the one of + // the SCC of S2 (called the "threshold"). + int threshold = p.first.second; + // TODO optimize if this is a self-loop? + while (threshold < ecs_->root.top().index) + { + assert(!ecs_->root.empty()); + if (strength == STRONG) + { + assert(!arc.empty()); + acc |= ecs_->root.top().condition; + acc |= arc.top(); + arc.pop(); + } + ecs_->root.pop(); + } + // Note that we do not always have + // threshold == root.top().index + // after this loop, the SCC whose index is threshold might have + // been merged with a lower SCC. + + // Accumulate all acceptance conditions into the merged SCC. + ecs_->root.top().condition |= acc; + + if (ecs_->aut->acc().accepting(ecs_->root.top().condition)) + { + // We have found an accepting SCC. + // Release all iterators in todo. + while (!todo.empty()) + { + T::it_destroy(ecs_->aut, todo.top().second); + todo.pop(); + dec_depth(); + } + // Use this state to start the computation of an + // accepting cycle. + ecs_->cycle_seed = p.first.first; + set_states(states()); + if (need_accepting_run) + return check_result( + std::make_shared>(ecs_)); + else + return true; + } + } + // This automaton recognizes no word. + set_states(states()); + return nullptr; + } + }; + + } // anonymous namespace + + emptiness_check_ptr + get_couvreur99_new(const const_twa_ptr& a, spot::option_map o) + { + const_twa_graph_ptr ag = std::dynamic_pointer_cast(a); + if (ag) + // the automaton is explicit + { + // NB: The order of the if's matter. + if (a->prop_terminal()) + return std::make_shared>(ag, o); + if (a->prop_weak()) + return std::make_shared>(ag, o); + return std::make_shared>(ag, o); + } + else + // the automaton is abstract + { + // NB: The order of the if's matter. + if (a->prop_terminal()) + return std::make_shared>(a, o); + if (a->prop_weak()) + return std::make_shared>(a, o); + return std::make_shared>(a, o); + } + } + + emptiness_check_result_ptr + couvreur99_new_check(const const_twa_ptr& a) + { + return get_couvreur99_new(a, spot::option_map())->check(); + } + + emptiness_check_ptr + get_couvreur99_new_abstract(const const_twa_ptr& a, option_map o) + { + if (a->prop_terminal()) + return std::make_shared>(a, o); + if (a->prop_weak()) + return std::make_shared>(a, o); + return std::make_shared>(a, o); + } + +} // namespace spot diff --git a/spot/twaalgos/couvreurnew.hh b/spot/twaalgos/couvreurnew.hh new file mode 100644 index 000000000..18b451bff --- /dev/null +++ b/spot/twaalgos/couvreurnew.hh @@ -0,0 +1,49 @@ +// -*- coding: utf-8 -*- +// Copyright (C) 2016 Laboratoire de Recherche et Developpement de l'EPITA. +// +// This file is part of Spot, a model checking library. +// +// Spot 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 3 of the License, or +// (at your option) any later version. +// +// Spot 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 this program. If not, see . + +#pragma once + +#include + +namespace spot +{ + /// \brief A rewritten version of the Couvreur emptiness check. + /// + /// It is optimized to run on explicit automata (avoiding the memory + /// allocations of the virtual, abstract interface. + /// It also has specializations for weak and terminal automata. + SPOT_API + emptiness_check_ptr + get_couvreur99_new(const const_twa_ptr& a, option_map o); + + /// \brief Same as above, but always uses the abstract interface. + /// + /// This function is provided to test the efficiency of specializing our + /// algorithms for explicit automata. It uses the same optimizations for + /// weak and terminal automata as the one above. + SPOT_API + emptiness_check_ptr + get_couvreur99_new_abstract(const const_twa_ptr& a, option_map o); + + /// \brief A shortcut to run the optimized emptiness check directly. + /// + /// This is the same as get_couvreur99_new(a, {})->check(). + SPOT_API + emptiness_check_result_ptr + couvreur99_new_check(const const_twa_ptr& a); +} diff --git a/spot/twaalgos/emptiness.cc b/spot/twaalgos/emptiness.cc index 5f14eb535..a9934946d 100644 --- a/spot/twaalgos/emptiness.cc +++ b/spot/twaalgos/emptiness.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -129,6 +130,8 @@ namespace spot ec_algo ec_algos[] = { { "Cou99", couvreur99, 0, -1U }, + { "Cou99new", get_couvreur99_new, 0, -1U }, + { "Cou99abs", get_couvreur99_new_abstract, 0, -1U }, { "CVWY90", magic_search, 0, 1 }, { "GV04", explicit_gv04_check, 0, 1 }, { "SE05", se05, 0, 1 }, diff --git a/tests/core/randtgba.cc b/tests/core/randtgba.cc index c13135e1a..b1ef86d6b 100644 --- a/tests/core/randtgba.cc +++ b/tests/core/randtgba.cc @@ -65,6 +65,8 @@ const char* default_algos[] = { "Cou99(poprem)", "Cou99(poprem shy !group)", "Cou99(poprem shy group)", + "Cou99new", + "Cou99abs", "CVWY90", "CVWY90(bsh=4K)", "GV04",