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.
This commit is contained in:
Maximilien Colange 2016-11-25 11:52:11 +01:00
parent f0416b3f3c
commit 1a08eca840
6 changed files with 927 additions and 0 deletions

7
NEWS
View file

@ -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

View file

@ -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 \

View file

@ -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 <http://www.gnu.org/licenses/>.
#include <spot/twaalgos/couvreurnew.hh>
#include <spot/twa/twa.hh>
#include <spot/twa/twagraph.hh>
#include <spot/twaalgos/bfssteps.hh>
#include <spot/twaalgos/emptiness.hh>
#include <spot/twaalgos/emptiness_stats.hh>
#include <stack>
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<bool is_explicit>
class twa_iteration
{
};
template<>
class twa_iteration<false>
{
public:
using state_t = const state*;
using iterator_t = twa_succ_iterator*;
template<class val>
using state_map = state_map<val>;
template<class val>
static
std::pair<std::pair<state_t, val>, bool>
h_emplace(state_map<val>& h, state_t s, val i)
{
auto p = h.emplace(s, i);
return std::make_pair(*p.first, p.second);
}
static
std::pair<state_t, int>
h_find(const state_map<int>& 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<int>& h, const std::function<bool(int)>& 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<true>
{
public:
using state_t = unsigned;
using iterator_t = explicitproxy;
template<class val>
using state_map = std::vector<val>;
template<class val>
static
std::pair<std::pair<state_t, val>, bool>
h_emplace(state_map<val>& 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<state_t, int>
h_find(const state_map<int>& h, state_t s)
{
assert(s < h.size());
return std::make_pair(s, h[s]);
}
static
unsigned
h_count(const state_map<int>& h, const std::function<bool(int)>& 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<bool is_explicit>
using automaton_ptr = typename std::conditional<is_explicit,
const_twa_graph_ptr,
const_twa_ptr>::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<bool is_explicit>
struct couvreur99_new_status
{
using T = twa_iteration<is_explicit>;
couvreur99_new_status(const automaton_ptr<is_explicit>& a)
: aut(a)
{
// Appropriate version is resolved through SFINAE.
init<is_explicit>();
}
automaton_ptr<is_explicit> aut;
std::stack<scc> root;
typename T::template state_map<int> h;
typename T::state_t cycle_seed;
private:
template<bool U>
typename std::enable_if<U>::type
init()
{
// Initialize h with the right size.
h.resize(aut->num_states(), 0);
}
template<bool U>
typename std::enable_if<!U>::type
init()
{}
};
template<bool is_explicit>
using couvreur99_new_status_ptr =
std::shared_ptr<couvreur99_new_status<is_explicit>>;
template<bool is_explicit>
class couvreur99_new_result final :
public emptiness_check_result,
public acss_statistics
{
using T = twa_iteration<is_explicit>;
public:
couvreur99_new_result(const couvreur99_new_status_ptr<is_explicit>& 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<is_explicit> 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<T>::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<is_explicit>* ecs;
couvreur99_new_result<is_explicit>* r;
acc_cond::mark_t& acc_to_traverse;
int scc_root;
scc_bfs(const couvreur99_new_status<is_explicit>* ecs,
couvreur99_new_result<is_explicit>* 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<bool is_explicit>
class shortest_path final : public bfs_steps
{
using T = twa_iteration<is_explicit>;
public:
shortest_path(const state_set* t,
const couvreur99_new_status_ptr<is_explicit>& ecs,
couvreur99_new_result<is_explicit>* 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<is_explicit> ecs;
couvreur99_new_result<is_explicit>* r;
};
template<bool is_explicit>
twa_run_ptr
couvreur99_new_result<is_explicit>::accepting_run()
{
run_ = std::make_shared<twa_run>(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<is_explicit> 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<bool is_explicit, twa_strength strength>
class couvreur99_new : public emptiness_check, public ec_statistics
{
using T = twa_iteration<is_explicit>;
using state_t = typename T::state_t;
using iterator_t = typename T::iterator_t;
using pair_state_iter = std::pair<state_t, iterator_t>;
couvreur99_new_status_ptr<is_explicit> ecs_;
public:
couvreur99_new(const automaton_ptr<is_explicit>& a,
option_map o = option_map())
: emptiness_check(a, o)
, ecs_(std::make_shared<couvreur99_new_status<is_explicit>>(a))
{
}
virtual
emptiness_check_result_ptr
check() override
{
return check_impl<true>();
}
// 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<false>();
}
twa_run
accepting_run()
{
return check_impl<true>()->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<bool need_accepting_run>
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<acc_cond::mark_t> 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<pair_state_iter> todo;
// * live, a stack of live nodes
std::deque<state_t> 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<couvreur99_new_result<is_explicit>>(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<const twa_graph>(a);
if (ag)
// the automaton is explicit
{
// NB: The order of the if's matter.
if (a->prop_terminal())
return std::make_shared<couvreur99_new<true, TERMINAL>>(ag, o);
if (a->prop_weak())
return std::make_shared<couvreur99_new<true, WEAK>>(ag, o);
return std::make_shared<couvreur99_new<true, STRONG>>(ag, o);
}
else
// the automaton is abstract
{
// NB: The order of the if's matter.
if (a->prop_terminal())
return std::make_shared<couvreur99_new<false, TERMINAL>>(a, o);
if (a->prop_weak())
return std::make_shared<couvreur99_new<false, WEAK>>(a, o);
return std::make_shared<couvreur99_new<false, STRONG>>(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<couvreur99_new<false, TERMINAL>>(a, o);
if (a->prop_weak())
return std::make_shared<couvreur99_new<false, WEAK>>(a, o);
return std::make_shared<couvreur99_new<false, STRONG>>(a, o);
}
} // namespace spot

View file

@ -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 <http://www.gnu.org/licenses/>.
#pragma once
#include <spot/twaalgos/emptiness.hh>
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);
}

View file

@ -24,6 +24,7 @@
#include <memory>
#include <spot/twaalgos/emptiness.hh>
#include <spot/twaalgos/bfssteps.hh>
#include <spot/twaalgos/couvreurnew.hh>
#include <spot/twaalgos/gtec/gtec.hh>
#include <spot/twaalgos/gv04.hh>
#include <spot/twaalgos/magic.hh>
@ -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 },

View file

@ -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",