property: implement cache for automata

Some properties of an automaton will now be cached. For now, only "is_proper"
is cached.

* vcsn/core/property-cache.hh: Here. It is possible to change values in
  the cache, to update values defined for algorithm and to print a specific
  property.

* lib/vcsn/misc/property-indexer.cc,
* vcsn/misc/property-indexer.hh: Singleton generating properties' indices.

* lib/vcsn/misc/property.cc,
* vcsn/misc/property.hh: Define properties and default 'function_prop', which
  specifies predefined values for properties when an algorithm/function is
  applied on the automatom.

* vcsn/core/mutable-automaton.hh: Add a property cache.

* vcsn/core/automaton-decorator.hh: Forward property cache accessor call.

* vcsn/core/permutation-automaton.hh: Copy input property cache.

* vcsn/algos/filter.hh,
* vcsn/algos/transpose.hh: Add a specific property cache.

* vcsn/algos/accessible.hh,
* vcsn/algos/add.hh,
* vcsn/algos/cerny.hh,
* vcsn/algos/complement.hh,
* vcsn/algos/complete.hh,
* vcsn/algos/compose.hh,
* vcsn/algos/conjugate.hh,
* vcsn/algos/conjunction.hh,
* vcsn/algos/copy.hh,
* vcsn/algos/de-bruijn.hh,
* vcsn/algos/derived-term.hh,
* vcsn/algos/determinize.hh,
* vcsn/algos/divkbaseb.hh,
* vcsn/algos/double-ring.hh,
* vcsn/algos/inductive.hh,
* vcsn/algos/insplit.hh,
* vcsn/algos/is-proper.hh,
* vcsn/algos/ladybird.hh,
* vcsn/algos/letterize.hh,
* vcsn/algos/lift.hh,
* vcsn/algos/minimize-brzozowski.hh,
* vcsn/algos/minimize-hopcroft.hh,
* vcsn/algos/minimize.hh,
* vcsn/algos/multiply.hh,
* vcsn/algos/normalize.hh,
* vcsn/algos/pair.hh,
* vcsn/algos/partial-identity.hh,
* vcsn/algos/prefix.hh,
* vcsn/algos/project-automaton.hh,
* vcsn/algos/proper.hh,
* vcsn/algos/push-weights.hh,
* vcsn/algos/quotient.hh,
* vcsn/algos/quotkbaseb.hh,
* vcsn/algos/reduce.hh,
* vcsn/algos/sort.hh
* vcsn/algos/standard.hh,
* vcsn/algos/star.hh,
* vcsn/algos/synchronize.hh,
* vcsn/algos/thompson.hh,
* vcsn/algos/to-expression.hh,
* vcsn/algos/to-spontaneous.hh,
* vcsn/algos/trie.hh,
* vcsn/algos/tuple-automaton.hh,
* vcsn/algos/u.hh,
* vcsn/algos/universal.hh,
* vcsn/algos/weight.hh: Define function tags and properties, update property
  cache.

* vcsn/algos/info.hh: Print properties (only is_proper for now).

* doc/notebooks/automaton.info.ipynb,
* tests/python/info.py,
* tests/python/project.py,
* tests/python/transpose.py,
* tests/unit/proper.chk: Update 'is proper' value.

* tests/python/is-proper.py,
* tests/python/lift.py,
* tests/python/proper.py: Check property cache value.

* lib/vcsn/local.mk,
* vcsn/local.mk: Add files.
parent 8595841d
......@@ -199,7 +199,7 @@
" 'is empty': False,\n",
" 'is eps-acyclic': True,\n",
" 'is normalized': False,\n",
" 'is proper': True,\n",
" 'is proper': 'N/A',\n",
" 'is standard': True,\n",
" 'is trim': True,\n",
" 'is useless': False,\n",
......@@ -267,7 +267,7 @@
" 'is empty': False,\n",
" 'is eps-acyclic': True,\n",
" 'is normalized': False,\n",
" 'is proper': True,\n",
" 'is proper': 'N/A',\n",
" 'is standard': True,\n",
" 'is synchronizing': True,\n",
" 'is trim': True,\n",
......
......@@ -67,6 +67,7 @@ lib_libvcsn_la_SOURCES = \
%D%/misc/flex-lexer.hh \
%D%/misc/format.cc \
%D%/misc/indent.cc \
%D%/misc/property-indexer.cc \
%D%/misc/random.cc \
%D%/misc/signature.cc \
%D%/misc/stream.cc \
......
#include <vcsn/misc/property-indexer.hh>
namespace vcsn
{
namespace detail
{
property_indexer& property_indexer::instance()
{
static property_indexer instance;
return instance;
}
property_indexer::index_t property_indexer::id()
{
return instance().count++;
}
} // namespace detail
} // namespace vcsn
......@@ -28,7 +28,7 @@ check(vcsn.context('lal_char(:a-z), q')
'is empty': False,
'is eps-acyclic': True,
'is normalized': False,
'is proper': True,
'is proper': 'N/A',
'is standard': True,
'is synchronizing': False,
'is trim': True,
......@@ -60,7 +60,7 @@ check(vcsn.context('lal_char(a-z), q')
'is empty': False,
'is eps-acyclic': True,
'is normalized': False,
'is proper': True,
'is proper': 'N/A',
'is standard': True,
'is synchronizing': False,
'is trim': True,
......@@ -93,7 +93,7 @@ check(vcsn.context('law_char(ab), b')
'is empty': False,
'is eps-acyclic': True,
'is normalized': False,
'is proper': True,
'is proper': 'N/A',
'is standard': True,
'is synchronizing': 'N/A',
'is trim': True,
......
......@@ -8,7 +8,10 @@ from test import *
# --------------------
# Check that vcsn is-proper gives EXPECTED.
def check(exp, aut):
CHECK_EQ(exp, vcsn.automaton(aut).is_proper())
a = vcsn.automaton(aut)
CHECK_EQ('N/A', a.info('is proper'))
CHECK_EQ(exp, a.is_proper())
CHECK_EQ(exp, a.info('is proper'))
def check_context(exp, aut, ctx):
print('Context: {}'.format(ctx))
......
......@@ -3,12 +3,17 @@
import vcsn
from test import *
# Check lift result and cached value for is_proper.
def check_lift(exp, aut):
CHECK_EQ(exp, aut)
CHECK_EQ(False, aut.info('is proper'))
## ---------- ##
## Automata. ##
## ---------- ##
l4 = vcsn.context('lal_char(abc), b').ladybird(4)
CHECK_EQ('''digraph
check_lift('''digraph
{
vcsn_context = "lao, expressionset<letterset<char_letters(abc)>, b>"
rankdir = LR
......@@ -67,7 +72,7 @@ aref = '''digraph
0 -> 1 [label = "<<2>(a)>d|gh"]
1 -> F1
}'''
CHECK_EQ(aref, a.lift(0))
check_lift(aref, a.lift(0))
# lift(1).
aref = '''digraph
......@@ -89,7 +94,7 @@ aref = '''digraph
0 -> 1 [label = "<<2>(d)>a|gh"]
1 -> F1
}'''
CHECK_EQ(aref, a.lift(1))
check_lift(aref, a.lift(1))
# lift(1, 2).
aref = '''digraph
......@@ -111,8 +116,8 @@ aref = '''digraph
0 -> 1 [label = "<<2>(d|gh)>a"]
1 -> F1
}'''
CHECK_EQ(aref, a.lift(1, 2))
CHECK_EQ(aref, a.lift([1, 2]))
check_lift(aref, a.lift(1, 2))
check_lift(aref, a.lift([1, 2]))
## ------------------ ##
## lift(expression). ##
......
......@@ -62,7 +62,7 @@ def check_aut(function_name, type_):
'is empty': False,
'is eps-acyclic': True,
'is normalized': True,
'is proper': True,
'is proper': 'N/A',
'is standard': True,
'is synchronizing': False,
'is trim': True,
......
......@@ -17,14 +17,24 @@ def check_algo(i, o, algo):
# We call sort().strip() everywhere to avoid seeing differences
# caused by the different numbering of the states between the
# algorithms.
CHECK_EQ(o.sort().strip(), i.proper(algo=algo).sort().strip())
iprop = i.proper(algo=algo)
CHECK_EQ(o.sort().strip(), iprop.sort().strip())
print("checking proper cache")
CHECK_EQ(True, iprop.info('is proper'))
# Since we remove only states that _become_ inaccessible,
# i.proper(prune=False).accessible() is not the same as i.proper():
# in the former case we also removed the non-accessible states.
print("checking proper(prune=False)")
CHECK_EQ(o.accessible(),
i.proper(prune=False, algo=algo).accessible())
iprop = i.proper(prune=False, algo=algo).accessible()
CHECK_EQ(o.accessible(), iprop)
print("checking proper(prune=False).accessible() cache")
# Check that proper is invalidated.
CHECK_EQ('N/A', iprop.info('is proper'))
# FIXME: Because proper uses copy, state numbers are changed.
#
......
......@@ -54,7 +54,7 @@ CHECK_EQ({
'is empty': False,
'is eps-acyclic': True,
'is normalized': False,
'is proper': True,
'is proper': 'N/A',
'is standard': False,
'is trim': True,
'is useless': False,
......
......@@ -20,7 +20,7 @@ is codeterministic: 1
is empty: 0
is eps-acyclic: 1
is normalized: 0
is proper: 1
is proper: N/A
is standard: 0
is trim: 1
is useless: 0
......@@ -116,7 +116,7 @@ is codeterministic: N/A
is empty: 0
is eps-acyclic: 0
is normalized: 0
is proper: 0
is proper: N/A
is standard: 0
is trim: 1
is useless: 0
......@@ -212,7 +212,7 @@ is codeterministic: 1
is empty: 0
is eps-acyclic: 1
is normalized: 0
is proper: 1
is proper: N/A
is standard: 0
is trim: 1
is useless: 0
......@@ -285,7 +285,7 @@ is codeterministic: N/A
is empty: 0
is eps-acyclic: 0
is normalized: 0
is proper: 0
is proper: N/A
is standard: 0
is trim: 1
is useless: 0
......@@ -407,7 +407,7 @@ is codeterministic: N/A
is empty: 0
is eps-acyclic: 0
is normalized: 0
is proper: 0
is proper: N/A
is standard: 0
is trim: 1
is useless: 0
......
......@@ -10,6 +10,13 @@
namespace vcsn
{
/*----------------.
| Function tags. |
`----------------*/
CREATE_FUNCTION_TAG(accessible);
CREATE_FUNCTION_TAG(coaccessible);
CREATE_FUNCTION_TAG(trim);
/*--------------------------------------------------.
| Sets of accessible, coaccessible, useful states. |
......@@ -133,7 +140,9 @@ namespace vcsn
filter_automaton<Aut>
accessible(const Aut& a)
{
return vcsn::filter(a, accessible_states(a));
auto res = vcsn::filter(a, accessible_states(a));
res->properties().update(accessible_ftag{});
return res;
}
/// Coaccessible part of an automaton.
......@@ -141,7 +150,9 @@ namespace vcsn
filter_automaton<Aut>
coaccessible(const Aut& a)
{
return vcsn::filter(a, coaccessible_states(a));
auto res = vcsn::filter(a, coaccessible_states(a));
res->properties().update(coaccessible_ftag{});
return res;
}
/// Useful part of an automaton.
......@@ -149,7 +160,9 @@ namespace vcsn
filter_automaton<Aut>
trim(const Aut& a)
{
return vcsn::filter(a, useful_states(a));
auto res = vcsn::filter(a, useful_states(a));
res->properties().update(trim_ftag{});
return res;
}
/*----------------------------------------------------------------.
......
......@@ -14,6 +14,13 @@
namespace vcsn
{
/*------------------------------.
| Function tag and properties. |
`------------------------------*/
// Defined in conjunction.hh
// Invalidate all properties.
struct add_ftag;
/*----------------------------.
| add(automaton, automaton). |
......@@ -54,6 +61,7 @@ namespace vcsn
b->label_of(t),
res->weightset()->conv(*b->weightset(),
b->weight_of(t)));
res->properties().update(add_ftag{});
return res;
}
......@@ -95,6 +103,7 @@ namespace vcsn
add_here(Aut1& res, const Aut2& b, general_tag)
{
copy_into(b, res);
res->properties().update(add_ftag{});
return res;
}
......
......@@ -6,6 +6,11 @@
namespace vcsn
{
/*---------------.
| Function tag. |
`---------------*/
CREATE_FUNCTION_TAG(cerny);
/*--------.
| cerny. |
......@@ -50,6 +55,7 @@ namespace vcsn
res->set_initial(states[0]);
res->set_final(states[0]);
res->properties().update(cerny_ftag{});
return res;
}
......
......@@ -11,6 +11,11 @@
namespace vcsn
{
/*---------------.
| Function tag. |
`---------------*/
CREATE_FUNCTION_TAG(complement);
/*------------------------.
| complement(automaton). |
......@@ -41,6 +46,8 @@ namespace vcsn
aut->unset_final(s);
else
aut->set_final(s);
aut->properties().update(complement_ftag{});
}
template <Automaton Aut>
......
......@@ -7,6 +7,12 @@
namespace vcsn
{
/*---------------.
| Function tag. |
`---------------*/
CREATE_FUNCTION_TAG(complete);
/// Complete \a aut and return it.
template <Automaton Aut>
Aut&
......@@ -56,6 +62,7 @@ namespace vcsn
for (auto letter : ls.generators())
aut->new_transition(sink, sink, letter);
aut->properties().update(complete_ftag{});
return aut;
}
......
......@@ -15,6 +15,12 @@
namespace vcsn
{
/*---------------.
| Function tag. |
`---------------*/
CREATE_FUNCTION_TAG(compose);
namespace detail
{
#define DEFINE(Type) \
......@@ -471,6 +477,7 @@ namespace vcsn
{
auto res = make_compose_automaton<false, OutTape, InTape>(lhs, rhs);
res->compose();
res->properties().update(compose_ftag{});
return res->strip();
}
......@@ -482,6 +489,7 @@ namespace vcsn
{
auto res = make_compose_automaton<true, OutTape, InTape>(lhs, rhs);
res->compose();
res->properties().update(compose_ftag{});
return res;
}
......
......@@ -5,6 +5,12 @@
namespace vcsn
{
/*----------------.
| Function tag . |
`----------------*/
CREATE_FUNCTION_TAG(conjugate);
template <Automaton Aut>
Aut
conjugate(const Aut& aut)
......@@ -46,6 +52,7 @@ namespace vcsn
ls.one(), ws.one());
}
res->properties().update(conjugate_ftag{});
return res;
}
......
......@@ -26,6 +26,17 @@
namespace vcsn
{
/*----------------.
| Function tags. |
`----------------*/
CREATE_FUNCTION_TAG(add);
CREATE_FUNCTION_TAG(conjunction);
CREATE_FUNCTION_TAG(infiltrate);
CREATE_FUNCTION_TAG(ldivide);
CREATE_FUNCTION_TAG(rdivide);
CREATE_FUNCTION_TAG(shuffle);
namespace detail
{
/*---------------------------------.
......@@ -108,6 +119,7 @@ namespace vcsn
add_conjunction_transitions(std::get<1>(p), std::get<0>(p));
aut_->todo_.pop_front();
}
aut_->properties().update(conjunction_ftag{});
}
/// Compute the left quotient
......@@ -124,6 +136,7 @@ namespace vcsn
add_ldivide_transitions(std::get<1>(p), std::get<0>(p));
aut_->todo_.pop_front();
}
aut_->properties().update(ldivide_ftag{});
}
/// Compute the deterministic sum of two deterministic automata.
......@@ -143,6 +156,7 @@ namespace vcsn
add_add_transitions(std::get<1>(p), std::get<0>(p));
aut_->todo_.pop_front();
}
aut_->properties().update(add_ftag{});
}
/// Compute the left quotient in-place.
......@@ -173,6 +187,7 @@ namespace vcsn
rhs->unset_initial(rhs->dst_of(t));
for (auto s: new_initials)
rhs->set_initial(s);
rhs->properties().update(ldivide_ftag{});
}
/// Compute the (accessible part of the) shuffle product.
......@@ -199,6 +214,7 @@ namespace vcsn
add_shuffle_transitions<false>(std::get<1>(p), std::get<0>(p));
aut_->todo_.pop_front();
}
aut_->properties().update(shuffle_ftag{});
}
/// Compute the (accessible part of the) infiltration product.
......@@ -255,6 +271,7 @@ namespace vcsn
aut_->todo_.pop_front();
}
aut_->properties().update(infiltrate_ftag{});
}
/// Tell lazy_tuple_automaton how to add the transitions to a state
......@@ -754,7 +771,9 @@ namespace vcsn
{
auto a1t = transpose(a1);
auto a2t = transpose(a2);
return transpose(ldivide(a2t, a1t));
auto res = transpose(ldivide(a2t, a1t));
res->properties().update(rdivide_ftag{});
return res;
}
namespace dyn
......
......@@ -213,6 +213,7 @@ namespace vcsn
{
operator()([](state_t_of<in_automaton_t>) { return true; },
[](transition_t_of<in_automaton_t>) { return true; });
out_->properties() = in_->properties();
}
/// A map from original state to result state.
......
......@@ -11,6 +11,12 @@
namespace vcsn
{
/*---------------.
| Function tag. |
`---------------*/
struct de_bruijn_ftag{};
// (a+b)*a(a+b)^n.
template <typename Context>
mutable_automaton<Context>
......@@ -40,6 +46,7 @@ namespace vcsn
prev = next;
}
res->set_final(prev);
res->properties().update(de_bruijn_ftag{});
return res;
}
......
......@@ -16,6 +16,12 @@
namespace vcsn
{
/*---------------.
| Function tag. |
`---------------*/
CREATE_FUNCTION_TAG(derived_term);
namespace detail
{
/// Specify a variety of derived-term construction.
......@@ -195,6 +201,7 @@ namespace vcsn
aut_->todo_.pop();
complete_via_derivation_(p.first, p.second);
}
aut_->properties().update(derived_term_ftag{});
return aut_;
}
......@@ -208,6 +215,7 @@ namespace vcsn
aut_->todo_.pop();
complete_via_expansion_(p.first, p.second);
}
aut_->properties().update(derived_term_ftag{});
return aut_;
}
......
......@@ -16,6 +16,13 @@
namespace vcsn
{
/*----------------.
| Function tags. |
`----------------*/
CREATE_FUNCTION_TAG(codeterminize);
CREATE_FUNCTION_TAG(determinize);
/*----------------------.
| subset construction. |
`----------------------*/
......@@ -106,6 +113,7 @@ namespace vcsn
aut_->todo_.pop();
complete_(src, ss);
}
aut_->properties().update(determinize_ftag{});
}
/// All the outgoing transitions.
......@@ -375,7 +383,9 @@ namespace vcsn
auto
codeterminize(const Aut& aut, Tag tag = {})
{
return transpose(determinize(transpose(aut), tag));
auto res = transpose(determinize(transpose(aut), tag));
res->properties().update(codeterminize_ftag{});
return res;
}
/*---------------------.
......
......@@ -8,6 +8,11 @@
namespace vcsn
{
/*---------------.
| Function tag. |
`---------------*/
CREATE_FUNCTION_TAG(divkbaseb);
/// Build the Boolean automaton which accepts a word n representing a
/// number in base "base" if and only if divisor|n.
......@@ -55,6 +60,7 @@ namespace vcsn
res->new_transition(states[i], states[d], letters[l]);
}
}
res->properties().update(divkbaseb_ftag{});
return res;
}
......
......@@ -11,6 +11,12 @@
namespace vcsn
{
/*---------------.
| Function tag. |
`---------------*/
CREATE_FUNCTION_TAG(double_ring);
template <typename Context>
mutable_automaton<Context>
double_ring(const Context& ctx, unsigned n,
......@@ -62,6 +68,7 @@ namespace vcsn
res->set_final(states[f]);
}