Commit 090a4599 authored by Akim Demaille's avatar Akim Demaille
Browse files

derived-term: implement laziness support

* vcsn/algos/derived-term.hh: Implement lazy support.

* lib/vcsn/dyn/context-parser.cc, lib/vcsn/dyn/context-printer.cc:
Add support for derived_term_automaton.

* tests/bin/test.py (CHECK_NE): New.
* tests/python/derivation.py: Check lazy construction.

* libexec/vcsn-score: Bench lazy construction.
parent f6fe933b
......@@ -337,6 +337,14 @@ namespace vcsn
context_());
eat_('>');
}
// xxx_automaton<ExpresionSet>.
else if (prefix == "derived_term_automaton")
{
eat_("<expressionset");
res = std::make_shared<automaton>(prefix,
expressionset_());
eat_('>');
}
// xxx_automaton<Aut...>.
else if (prefix == "product_automaton"
|| prefix == "tuple_automaton")
......
......@@ -57,6 +57,7 @@ namespace vcsn
"automaton type",
{
{"delay_automaton" , "vcsn/algos/is-synchronized.hh"},
{"derived_term_automaton" , "vcsn/algos/derived-term.hh"},
{"determinized_automaton" , "vcsn/algos/determinize.hh"},
{"detweighted_automaton" , "vcsn/algos/determinize.hh"},
{"expression_automaton" , "vcsn/core/expression-automaton.hh"},
......
......@@ -186,7 +186,11 @@ for fmt in ['dot', 'efsm', 'fado']:
def bench_derived_term(alphabet, exp, algo, number):
ctx = "lal_char({}), z".format(alphabet)
bench('r.derived_term("{}")'.format(algo),
cmd = 'r.derived_term("{}")'.format(algo)
if 'lazy' in algo:
# Accessible resolves the lazy states.
cmd = cmd + '.accessible()'
bench(cmd,
'r = {}, c = {}'.format(exp, ctx_signature(ctx)),
setup=['ctx = "{}"'.format(ctx),
'e = "{}"'.format(exp),
......@@ -207,9 +211,15 @@ bench_dt('a-z', 150, 'expansion', 20)
bench_dt('ab', 300, 'derivation', 10)
bench_dt('a-z', 300, 'derivation', 10)
bench_dt('a-z', 300, 'expansion', 10)
bench_dt('a-z', 300, 'lazy,expansion', 10)
# Measure the cost of a lazy construction.
bench_dt('a-z', 3, 'expansion', 5000)
bench_dt('a-z', 3, 'lazy,expansion', 5000)
bench_derived_term('a', 'a?{150}', 'derivation', 1)
bench_derived_term('a', 'a?{150}', 'expansion', 1)
bench_derived_term('a', 'a?{150}', 'expansion', 2)
bench_derived_term('a', 'a?{150}', 'lazy,expansion', 2)
# standard
ctx = 'lal_char(a-z), z'
......
......@@ -140,12 +140,25 @@ def CHECK_EQ(expected, effective, loc = None):
else:
exp = format(expected)
eff = format(effective)
msg = exp + " != " + eff
FAIL(loc=loc)
FAIL("Unexpected result", loc=loc)
rst_file("Expected output", exp)
rst_file("Effective output", eff)
rst_diff(exp, eff)
def CHECK_NE(expected, effective, loc = None):
"Check that `effective` is not equal to `expected`."
if isinstance(expected, str) and not isinstance(effective, str):
effective = str(effective)
if expected != effective:
PASS(loc=loc)
else:
exp = format(expected)
eff = format(effective)
FAIL("Unexpected equality", loc=loc)
rst_file("First argument", exp)
rst_file("Second argument", eff)
rst_diff(exp, eff)
def normalize(a):
'''Turn automaton `a` into something we can check equivalence with.'''
a = a.strip().realtime()
......
......@@ -6,12 +6,34 @@ from test import *
ctx = vcsn.context("lal_char(abc), seriesset<lal_char(xyz), q>")
def check_derived_term(r, exp, algo):
print("{}: Checking: {}.derived_term({})".format(here(), r, algo))
if algo == 'lazy,expansion':
# When checking the lazy construction, make sure we have a
# derived_term_automaton, then snapshot it at the beginning
# (just the initial states), then make sure that when we
# evaluate the empty word, we have something different.
#
# Finally, evaluate it completely (with accessible), and check
# against the strict computation.
lazy = r.derived_term(algo)
CHECK(lazy.type().startswith('derived_term_automaton'))
first = str(lazy)
# Force the evaluation of the empty word to start computing
# the automaton.
lazy('')
second = str(lazy)
CHECK_NE(second, first)
lazy.accessible()
CHECK_NE(lazy, second)
eff = lazy.strip()
else:
eff = r.derived_term(algo)
CHECK_EQ(open(medir + '/' + exp + '.gv').read().strip(),
r.derived_term(algo))
eff)
def check_dt(r, exp):
'Check derived-term automaton.'
for algo in ['derivation', 'expansion']:
for algo in ['derivation', 'expansion', 'lazy,expansion']:
check_derived_term(r, exp, algo)
def check_bdt(r, exp):
......
......@@ -35,7 +35,7 @@ namespace vcsn
{}
/// From algo name to algo.
derived_term_algo(const std::string& algo)
derived_term_algo(std::string algo)
{
static const auto map = std::map<std::string, derived_term_algo>
{
......@@ -52,6 +52,8 @@ namespace vcsn
{"expansion,deterministic", {expansion, false, true}},
{"expansion_breaking", {expansion, true, false}},
};
if (boost::starts_with(algo, "lazy,"))
algo = algo.substr(5);
*this = getargs("derived-term algorithm", map, algo);
}
......@@ -140,6 +142,20 @@ namespace vcsn
/// Base class.
using super_t = automaton_decorator<automaton_t>;
static symbol sname()
{
static auto res = symbol{"derived_term_automaton<"
+ expressionset_t::sname()
+ ">"};
return res;
}
std::ostream& print_set(std::ostream& o, format fmt = {}) const
{
o << "derived_term_automaton<";
return rs_.print_set(o, fmt) << '>';
}
derived_term_automaton_impl(const expressionset_t& rs,
derived_term_algo algo)
: super_t{make_shared_ptr<automaton_t>(rs)}
......@@ -183,13 +199,14 @@ namespace vcsn
return aut_;
}
private:
// private:
/// The expression_automaton we are building.
using super_t::aut_;
/// Initialize the computation: build the initial states.
void init_(const expression_t& expression)
{
done_.insert(aut_->pre());
if (algo_.breaking)
for (const auto& p: split(rs_, expression))
aut_->set_initial(label_of(p), weight_of(p));
......@@ -197,6 +214,31 @@ namespace vcsn
aut_->set_initial(expression, ws_.one());
}
/// Whether a given state's outgoing transitions have been
/// computed.
bool state_is_strict(state_t s) const
{
return has(done_, s);
}
/// Complete a state: find its outgoing transitions.
void complete_(state_t s) const
{
const auto& orig = aut_->origins();
auto sn = orig.at(s);
const_cast<self_t&>(*this).complete_via_expansion_(s, sn);
done_.insert(s);
}
/// All the outgoing transitions.
auto all_out(state_t s) const
-> decltype(vcsn::detail::all_out(aut_, s))
{
if (!state_is_strict(s))
complete_(s);
return vcsn::detail::all_out(aut_, s);
}
/// Compute the outgoing transitions of \a src.
template <typename ES = expressionset_t,
typename = std::enable_if<labelset_t_of<ES>::is_free()>>
......@@ -259,6 +301,11 @@ namespace vcsn
to_expansion_t to_expansion_ = {rs_};
/// Possibly the generators.
derived_term_automaton_members<expressionset_t> members_ = {rs_};
/// When performing the lazy construction, list of states that
/// have been completed (i.e., their outgoing transitions have
/// been computed).
mutable std::set<state_t> done_ = {aut_->post()};
};
}
......@@ -327,7 +374,20 @@ namespace vcsn
const auto& e = exp->as<ExpSet>();
const auto& rs = e.expressionset();
const auto& r = e.expression();
return make_automaton(::vcsn::derived_term(rs, r, algo));
if (boost::starts_with(algo, "lazy"))
{
auto a = vcsn::detail::derived_term_algo(algo);
require(a.algo == vcsn::detail::derived_term_algo::expansion,
"derived_term: laziness works only with expansions");
// Do not call the operator(), this would trigger the compilation
// of via_derivation, which does not compile (on purpose) for non
// free labelsets.
auto res = make_derived_term_automaton(rs, a);
res->init_(r);
return make_automaton(res);
}
else
return make_automaton(::vcsn::derived_term(rs, r, algo));
}
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment