Commit febae8b1 authored by Akim Demaille's avatar Akim Demaille
Browse files

split: fix the handling of extended expressions

Our handling of `&`, modeled after `.`, was dead wrong.  The point is
to capture all the sums that appear at the top-level, so `(a?)[bc]` is
split in `b⊕c⊕a(b+c)`.  This did require to splut `[bc]` because `a?`
is nullable.

However, doing the same with `&` splits `(a?)&[bc]` into
`b⊕c⊕a&(b+c)`, which is obviously wrong (e.g., the latter accepts `b`
but not the former).

Don't try to split extended operators.  Eventually, we might decide to
distribute from the left to the right, in which case `(a?)&[bc]`
should be split in `\e&[bc] ⊕ a&[bc]`, but not what we had currently.

See issue #149 about that.

* vcsn/algos/split.hh: Avoid recreating equivalent expressions:
just reuse the expression itself when there is nothing to do.
* tests/bin/test.py (shortest): New.
* tests/python/split.py (check): Check that the split expression is
equivalent to the expression itself.
Check extended operators.
parent 8bc18ce9
......@@ -240,10 +240,16 @@ def can_test_equivalence(a):
return False
return True
def shortest(e, num=20):
if isinstance(e, vcsn.automaton):
e = e.proper()
return e.shortest(num)
def CHECK_EQUIV(a1, a2):
'''Check that `a1` and `a2` are equivalent. Works for
two automata, or two expressions.'''
# If we cannot check equivalence, check equality of the `num`
# shortest monomials.
num = 20
# Cannot compute equivalence on Zmin, approximate with shortest.
try:
......@@ -251,11 +257,12 @@ def CHECK_EQUIV(a1, a2):
res = a1.is_equivalent(a2)
via = '(via is_equivalent)'
else:
res = a1.proper().shortest(num) == a2.proper().shortest(num)
res = shortest(a1, num) == shortest(a2, num)
via = '(via shortests)'
except RuntimeError as e:
FAIL("cannot check equivalence: " + str(e))
res = False
via = ''
if res:
PASS()
......@@ -264,8 +271,8 @@ def CHECK_EQUIV(a1, a2):
rst_file("Left", format(a1))
rst_file("Right", format(a2))
try:
s1 = a1.proper().shortest(num).format('list')
s2 = a2.proper().shortest(num).format('list')
s1 = shortest(a1, num).format('list')
s2 = shortest(a2, num).format('list')
rst_file("Left shortest", s1)
rst_file("Right shortest", s2)
rst_diff(s1, s2)
......
......@@ -3,30 +3,26 @@
import vcsn
from test import *
ctx = vcsn.context('lal_char(abc), expressionset<lal_char(wxyz), q>')
# We are checking the support for quotients, which requires the label
# one.
ctx = vcsn.context('lan, expressionset<lal, q>')
cexp = ctx.expression
# check INPUT [RESULT = INPUT]
# ----------------------------
# Check that the splitting of INPUT is RESULT.
def check(re, exp=None):
def check(e, exp=None):
if exp is None:
exp = re
r = ctx.expression(re)
s = r.split()
CHECK_EQ(exp, s)
# Split polynomials is idempotent.
CHECK_EQ(s, s.split())
# fail INPUT
# ----------
def fail(re):
re = ctx.expression(re)
XFAIL(lambda: re.split())
fail('a*{c}')
fail(r'a*{\}b*')
fail('a:b')
fail('a*{T}')
exp = e
if not isinstance(e, vcsn.expression):
e = cexp(e)
p = e.split()
CHECK_EQ(exp, p)
# The polynomial, turned into an expression, and the input
# expression are equivalent.
CHECK_EQUIV(cexp(str(p)), e)
# Splitting polynomials is idempotent.
CHECK_EQ(p, p.split())
check(r'\z')
check(r'<x>\e')
......@@ -36,12 +32,22 @@ check('<xy>a<z>b')
check('<x>a+<y>b', '<x>a + <y>b')
check('<x>a+<y>b+<z>a', '<x+z>a + <y>b')
check('(<w>a+<x>b)(<y>a+<z>b)', '<w>a(<y>a+<z>b) + <x>b(<y>a+<z>b)')
check('(<w>a+<x>b)&(<y>a+<z>b)', '<w>a&(<y>a+<z>b) + <x>b&(<y>a+<z>b)')
# The code is really different when there are more than two operands
# for conjunction.
check('(<w>a+<x>b)&(<y>a+<z>b)&(<y>a+<z>b)',
'<w>a&(<y>a+<z>b)&(<y>a+<z>b) + <x>b&(<y>a+<z>b)&(<y>a+<z>b)')
check('a*{c}')
check(r'a*{\}b*')
check('(a+b)&(c+d)')
check('(a+b):(c+d)')
check('(a+b)&:(c+d)')
check('a*{T}')
e0 = cexp(r'<E>\e')
e1 = cexp(r'<A>a')
e2 = cexp(r'<B>b')
check((e1 + e2) ** 2, '<A>a(<A>a+<B>b) + <B>b(<A>a+<B>b)')
check((e1 + e2) ** 3, '<A>a(<A>a+<B>b){2} + <B>b(<A>a+<B>b){2}')
check((e0 + e1 + e2) ** 2,
r'<EE>\e + <EA>a + <EB>b + <A>a(<E>\e+<A>a+<B>b) + <B>b(<E>\e+<A>a+<B>b)')
## --------------------- ##
## Documented examples. ##
......
......@@ -117,16 +117,6 @@ namespace vcsn
res_ = ps_.zero();
}
VCSN_RAT_VISIT(one,)
{
res_ = polynomial_t{{rs_.one(), ws_.one()}};
}
VCSN_RAT_VISIT(atom, e)
{
res_ = polynomial_t{{rs_.atom(e.value()), ws_.one()}};
}
VCSN_RAT_VISIT(add, e)
{
auto res = ps_.zero();
......@@ -138,12 +128,10 @@ namespace vcsn
res_ = std::move(res);
}
/// The split-product of \a l with \a r.
/// The split-multiplation of \a l with \a r.
///
/// Returns split(l) x split(r).
template <typename Product>
polynomial_t product(Product&& prod,
const expression_t& l, const expression_t& r)
polynomial_t multiply(const expression_t& l, const expression_t& r)
{
// B(l).
polynomial_t l_split = split(l);
......@@ -155,62 +143,54 @@ namespace vcsn
// res = proper(B(l)) x r.
auto res = ps_.zero();
for (const auto& e: l_split)
// FIXME: C++17: invoke.
ps_.add_here(res, (rs_.*prod)(label_of(e), r), weight_of(e));
ps_.add_here(res, rs_.mul(label_of(e), r), weight_of(e));
// res += ⟨constant-term(B(l))⟩.B(r)
ps_.add_here(res,
ps_.lweight(l_split_const, split(r)));
return res;
}
/// The split-product of \a l with \a r.
/// The split-multiplation of \a l with \a r.
///
/// Returns l x split(r).
template <typename Product>
polynomial_t product(Product&& prod,
const polynomial_t& l, const expression_t& r)
polynomial_t multiply(const polynomial_t& l, const expression_t& r)
{
auto res = ps_.zero();
/// FIXME: This is inefficient, we split the lhs way too often.
for (const auto& m: l)
ps_.add_here(res,
ps_.lweight(weight_of(m),
product(prod, label_of(m), r)));
return res;
}
/// The split-product of a variadic product.
template <typename Product, exp::type_t Type>
polynomial_t product(Product&& prod,
const variadic_t<Type>& e)
{
auto res = product(prod, e[0], e[1]);
for (unsigned i = 2, n = e.size(); i < n; ++i)
res = product(prod, res, e[i]);
multiply(label_of(m), r)));
return res;
}
/// Handle an n-ary multiplication.
VCSN_RAT_VISIT(mul, e)
{
using bin_t =
expression_t (expressionset_t::*)(const expression_t&,
const expression_t&) const;
res_ = product(static_cast<bin_t>(&expressionset_t::mul), e);
auto res = multiply(e[0], e[1]);
for (unsigned i = 2, n = e.size(); i < n; ++i)
res = multiply(res, e[i]);
res_ = std::move(res);
}
/// Handle an n-ary conjunction.
VCSN_RAT_VISIT(conjunction, e)
{
res_ = product(&expressionset_t::conjunction, e);
/// These are just propagated as monomials.
#define DEFINE(Type) \
VCSN_RAT_VISIT(Type, e) \
{ \
res_ = polynomial_t{{e.shared_from_this(), ws_.one()}}; \
}
VCSN_RAT_UNSUPPORTED(complement)
VCSN_RAT_UNSUPPORTED(compose)
VCSN_RAT_UNSUPPORTED(infiltrate)
VCSN_RAT_UNSUPPORTED(ldivide)
VCSN_RAT_UNSUPPORTED(shuffle)
VCSN_RAT_UNSUPPORTED(transposition)
DEFINE(atom)
DEFINE(complement)
DEFINE(compose)
DEFINE(conjunction)
DEFINE(infiltrate)
DEFINE(ldivide)
DEFINE(one)
DEFINE(shuffle)
DEFINE(star)
DEFINE(transposition)
#undef DEFINE
using tuple_t = typename super_t::tuple_t;
void visit(const tuple_t&, std::true_type) override
......@@ -218,11 +198,6 @@ namespace vcsn
raise(me(), ": tuple is not supported");
}
VCSN_RAT_VISIT(star, e)
{
res_ = polynomial_t{{e.shared_from_this(), ws_.one()}};
}
VCSN_RAT_VISIT(lweight, e)
{
e.sub()->accept(*this);
......
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