Commit 83b309f6 authored by Akim Demaille's avatar Akim Demaille
Browse files

proper: strip the nullable part of the result

Our (current) implementation of `proper` is in-place, and as a
consequence, the result features the same context as the input
automaton: a nullable labelset.

Now it return a labelset un-nullabled _if possible_.

To this end, `proper` now performs two copies: the initial one to be
able to modify the working copy of the input automaton, and a final
one to rewrite the automaton in the context with removed support for
spontaneous transitions.

There is no real noted performance regression:

   before  after
    2.39    2.41  a.proper()           # a = thompson(a?{1200})

* vcsn/algos/proper.hh (proper): Do that.
* vcsn/labelset/nullableset.hh: .
* python/vcsn/automaton.py (_lan_to_lal, proper): Remove, now useless.
* python/vcsn_cxx.cc (proper): Bind under its real name.
* vcsn/labelset/tupleset.hh (conv): Add support to strip an outter
nullableset.

* tests/bin/test.py (normalize): New.
(CHECK_EQUIV): Use it.
* tests/python/compose.py, tests/python/proper.py, bin/vcsn-score: Adjust.
parent 22427019
......@@ -6,6 +6,22 @@ Vaucanson 2, in reverse chronological order. On occasions, significant
changes in the internal API may also be documented.
## 2014-10-23
### proper: strip nullableset from labelsets
Our (current) implementation of `proper` is in-place, and as a
consequence, the result features the same context as the input
automaton, with a nullable labelset.
Now, `proper` copies the result in a context with a de-nullabled
labelset. So for instance:
In [2]: vcsn.context('lan_char(ab)_b').ratexp('(ab)*').thompson().proper()
Out[2]: mutable_automaton<lal_char(ab)_b>
There is no measurable performance regression. However state numbers
are now unrelated to the input automaton.
## 2014-10-20
### Python: loading automata from files
The `vcsn.automaton` constructor now support a `filename` named argument
......
......@@ -145,7 +145,7 @@ bench('a.sort()', 'a = std({})'.format(r),
# proper.
r = "a?{1200}"
a = vcsn.context("lan_char(a)_b").ratexp(r).thompson()
bench('a.proper()', 'a = thompson({})'.format(r), lambda: a._proper())
bench('a.proper()', 'a = thompson({})'.format(r), lambda: a.proper())
# to-ratexp.
r = '[a-d]?{100}'
......
......@@ -177,25 +177,6 @@ def _automaton_is_synchronized_by(self, w):
return self._is_synchronized_by(w)
automaton.is_synchronized_by = _automaton_is_synchronized_by
def _lan_to_lal(a):
'''Convert an automaton from supporting spontaneous transitions
to not supporting them by modifying its context specification.
'''
dot = a.format('dot')
dot = re.sub(r'"lan<(lal_char\(.*?\))>', r'"\1', dot)
dot = re.sub(r'"lat<lan<(lal_char\(.*?\))>, *lan<(lal_char\(.*?\))>',
r'"lat<\1, \2', dot)
dot = re.sub(r'"lan<(lat<lal_char\(.*?\)(?:, *lal_char\(.*?\))*>)>',
r'"\1', dot)
return automaton(dot, 'dot')
automaton.lan_to_lal = _lan_to_lal
# Somewhat cheating: in Python, proper returns a LAL, not a LAN.
# _proper is the genuine binding to dyn::proper.
def _automaton_proper(self, *args, **kwargs):
return self._proper(*args, **kwargs).lan_to_lal()
automaton.proper = _automaton_proper
automaton.shuffle = lambda *auts: automaton._shuffle(list(auts))
automaton.state_number = lambda self: self.info(False)['number of states']
......@@ -1011,7 +1011,7 @@ BOOST_PYTHON_MODULE(vcsn_cxx)
.def("prefix", &automaton::prefix)
.def("power", &automaton::power)
.def("_product", &automaton::product_).staticmethod("_product")
.def("_proper", &automaton::proper,
.def("proper", &automaton::proper,
(arg("prune") = true, arg("backward") = true))
.def("push_weights", &automaton::push_weights)
.def("ratexp", &automaton::to_ratexp, (arg("algo") = "auto"))
......
......@@ -6,6 +6,8 @@ import os
import re
import sys
import vcsn
count = 0
npass = 0
nfail = 0
......@@ -138,15 +140,21 @@ def CHECK_EQ(expected, effective, loc = None):
rst_file("Effective output", eff)
rst_diff(exp, eff)
def normalize(a):
'''Turn automaton `a` into something we can check equivalence with.'''
a = a.strip()
if 'lan' in str(a.context()):
a = a.proper()
# Eliminate nullablesets if there are that remain.
to = re.sub(r'lan<(lal_char\(.*?\))>', r'\1', a.context().format('text'))
return a.automaton(vcsn.context(to))
def CHECK_EQUIV(a1, a2):
'''Check that `a1` and `a2` are equivalent.'''
num = 10
a1 = a1.strip()
a2 = a2.strip()
if 'lan' in str(a1.context()):
a1 = a1.proper()
if 'lan' in str(a2.context()):
a2 = a2.proper()
a1 = normalize(a1)
a2 = normalize(a2)
# Cannot compare automata on Zmin.
if str(a1.context()).endswith('zmin') or str(a2.context()).endswith('zmin'):
......@@ -158,8 +166,8 @@ def CHECK_EQUIV(a1, a2):
PASS()
else:
FAIL("automata are not equivalent")
rst_file("Left automaton", a1)
rst_file("Right automaton", a2)
rst_file("Left automaton", a1.format('dot'))
rst_file("Right automaton", a2.format('dot'))
s1 = a1.shortest(num).format('list')
s2 = a2.shortest(num).format('list')
rst_file("Left automaton shortest", s1)
......
......@@ -66,9 +66,8 @@ check(c1.ratexp("'(a, x)'*").standard(), c2.ratexp("'(x, d)'*").standard(), a)
t1 = c1.ratexp("'(a, x)'*").thompson()
t2 = c2.ratexp("'(x, d)'*").thompson()
CHECK_EQ(re.compile("lan<(lal_char\(.*?\))>").sub("\\1",
str(vcsn.automaton(a).strip())),
t1.compose(t2).trim().proper().strip())
CHECK_EQ(vcsn.automaton(a),
t1.compose(t2).trim().proper())
check(c1.ratexp("'(a, x)'*").standard(), c2.ratexp("'(y, d)'*").standard(),
'''digraph
......
......@@ -54,6 +54,7 @@ a = load('lal_char_z/binary.gv')
check(a, 'binary.efsm')
for f in ["lal-char-z", "lat-z"]:
print("f:", f)
a = vcsn.automaton(filename = medir + "/" + f + '.gv')
check(a, f + '.efsm')
......
......@@ -7,12 +7,12 @@ from test import *
# ---------------
def check(i, o):
i = vcsn.automaton(i)
CHECK_EQ(o, i._proper())
CHECK_EQ(o, i.proper())
# FIXME: Because _proper uses copy, state numbers are changed.
#
# FIXME: cannot use is_isomorphic because there may be unreachable
# states.
CHECK_EQ(i._proper().sort().strip(), i._proper()._proper().sort().strip())
CHECK_EQ(i.proper().sort().strip(), i.proper().proper().sort().strip())
def check_fail(aut):
a = vcsn.automaton(aut)
......@@ -269,22 +269,22 @@ check(r'''digraph
4 -> F
}''', '''digraph
{
vcsn_context = "lan<lal_char(z)>_ratexpset<lal_char(abcd)_z>"
vcsn_context = "lal_char(z)_ratexpset<lal_char(abcd)_z>"
rankdir = LR
edge [arrowhead = vee, arrowsize = .6]
{
node [shape = point, width = 0]
I0
F4
F1
}
{
node [shape = circle, style = rounded, width = 0.5]
0
4
1
}
I0 -> 0
0 -> 4 [label = "<(abcd)*>z"]
4 -> F4
0 -> 1 [label = "<(abcd)*>z"]
1 -> F1
}''')
......@@ -324,24 +324,24 @@ check(r'''digraph
9 -> F
}''', '''digraph
{
vcsn_context = "lan<lal_char(z)>_ratexpset<lal_char(abcdefgh)_z>"
vcsn_context = "lal_char(z)_ratexpset<lal_char(abcdefgh)_z>"
rankdir = LR
edge [arrowhead = vee, arrowsize = .6]
{
node [shape = point, width = 0]
I0
F0
F7
F2
}
{
node [shape = circle, style = rounded, width = 0.5]
0
1 [color = DimGray]
7 [color = DimGray]
2 [color = DimGray]
}
I0 -> 0
0 -> F0 [label = "<beg>"]
7 -> F7 [label = "<fh>", color = DimGray]
2 -> F2 [label = "<fh>", color = DimGray]
}''')
......
......@@ -564,18 +564,63 @@ namespace vcsn
}
}
/// From a labelset, its non-nullable labelset.
///
/// Unfortunately cannot be always done. For instance,
/// tupleset<nullableset<letterset>, nullableset<letterset>> cannot
/// be turned in tupleset<letterset, letterset>, as it also forbids
/// (a, \e) and (\e, x) which should be kept legitimate.
template <typename LabelSet>
struct proper_labelset
{
using type = LabelSet;
static std::shared_ptr<const type>
value(const std::shared_ptr<const LabelSet>& ls)
{
return ls;
}
};
template <typename LabelSet>
struct proper_labelset<nullableset<LabelSet>>
{
using type = LabelSet;
static std::shared_ptr<const type>
value(const std::shared_ptr<const nullableset<LabelSet>>& ls)
{
return ls->labelset();
}
};
/// From a context, its non-nullable context.
template <typename LabelSet, typename WeightSet>
auto
proper_context(const context<LabelSet, WeightSet>& ctx)
-> context<typename proper_labelset<LabelSet>::type, WeightSet>
{
using proper_labelset = proper_labelset<LabelSet>;
std::shared_ptr<const typename proper_labelset::type> ls
= proper_labelset::value(ctx.labelset());
std::shared_ptr<const WeightSet> ws = ctx.weightset();
return {ls, ws};
}
/// Eliminate spontaneous transitions. Raise if the input automaton
/// is invalid.
template <typename Aut>
auto
proper(const Aut& aut, direction dir = direction::backward,
bool prune = true)
-> decltype(copy(aut))
-> mutable_automaton<decltype(proper_context(copy(aut)->context()))>
{
// FIXME: We could avoid copying if the automaton is already
// proper.
auto res = copy(aut);
proper_here(res, dir, prune);
auto a = copy(aut);
proper_here(a, dir, prune);
auto ctx = proper_context(a->context());
auto res = make_mutable_automaton(ctx);
copy_into(a, res);
return res;
}
......
......@@ -252,7 +252,7 @@ namespace vcsn
bool
is_valid(value_t v) const
{
return labelset()->is_valid(get_value(v)) || is_one(v);
return is_one(v) || labelset()->is_valid(get_value(v));
}
value_t
......@@ -267,6 +267,14 @@ namespace vcsn
return one();
}
/// Conversion from another type: let it be handled by our wrapped labelset.
template <typename LabelSet_>
value_t
conv(const LabelSet_& ls, typename LabelSet_::value_t v) const
{
return labelset()->conv(ls, v);
}
const labelset_ptr labelset() const
{
return ls_;
......@@ -424,7 +432,7 @@ namespace vcsn
return o;
}
private:
/// Return the value when it's not one.
static typename labelset_t::value_t
get_value(const value_t& v)
{
......
......@@ -39,7 +39,8 @@ namespace vcsn
struct labelset_types<decltype(pass{typename ValueSets::word_t()...}, void()),
ValueSets...>
{
using genset_t = cross_sequences<decltype(std::declval<ValueSets>().genset())...>;
using genset_t
= cross_sequences<decltype(std::declval<ValueSets>().genset())...>;
using letter_t = std::tuple<typename ValueSets::letter_t...>;
using word_t = std::tuple<typename ValueSets::word_t...>;
};
......@@ -382,6 +383,24 @@ namespace vcsn
return v ? one() : zero();
}
/// Convert a value from tupleset<...> to value_t.
template <typename... VS>
value_t
conv(const tupleset<VS...>& vs,
const typename tupleset<VS...>::value_t& v) const
{
return conv_(vs, v, indices);
}
/// Convert a value from nullableset<tupleset<...>> to value_t.
template <typename... VS>
value_t
conv(const nullableset<tupleset<VS...>>& vs,
const typename nullableset<tupleset<VS...>>::value_t& v) const
{
return conv(*vs.labelset(), vs.get_value(v));
}
/// Read one label from i, return the corresponding value.
value_t
conv(std::istream& i) const
......@@ -699,6 +718,15 @@ namespace vcsn
return word_t{set<I>().concat(std::get<I>(l), std::get<I>(r))...};
}
template <typename... VS, std::size_t... I>
value_t
conv_(const tupleset<VS...>& vs,
const typename tupleset<VS...>::value_t& v,
seq<I...>) const
{
return value_t{set<I>().conv(vs.template set<I>(), std::get<I>(v))...};
}
template <std::size_t... I>
value_t
conv_(std::istream& i, seq<I...>) const
......
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