### 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') # We are checking the support for quotients, which requires the label # one. ctx = vcsn.context('lan, expressionset') 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'\e') ... ... @@ -36,12 +32,22 @@ check('ab') check('a+b', 'a + b') check('a+b+a', 'a + b') check('(a+b)(a+b)', 'a(a+b) + b(a+b)') check('(a+b)&(a+b)', 'a&(a+b) + b&(a+b)') # The code is really different when there are more than two operands # for conjunction. check('(a+b)&(a+b)&(a+b)', 'a&(a+b)&(a+b) + b&(a+b)&(a+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') e1 = cexp(r'a') e2 = cexp(r'b') check((e1 + e2) ** 2, 'a(a+b) + b(a+b)') check((e1 + e2) ** 3, 'a(a+b){2} + b(a+b){2}') check((e0 + e1 + e2) ** 2, r'\e + a + b + a(\e+a+b) + b(\e+a+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 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 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 polynomial_t product(Product&& prod, const variadic_t& e) { auto res = product(prod, e, e); 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(&expressionset_t::mul), e); auto res = multiply(e, e); 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