tl: implement suffix operator normal form

* spot/tl/Makefile.am: New sonf files
* spot/tl/sonf.cc,spot/tl/sonf.hh: Here.
* python/spot/impl.i: include sonf.hh header
* doc/spot.bib: add entry for the SONF paper
* tests/python/formulas.ipynb: show sample usage
* tests/python/spot.py: test automata equivalence before/after SONF
* NEWS: mention the change
This commit is contained in:
Antoine Martin 2021-11-09 16:00:16 +01:00
parent c7201e4776
commit 0505ee9310
9 changed files with 348 additions and 0 deletions

4
NEWS
View file

@ -11,6 +11,10 @@ New in spot 2.10.4.dev (net yet released)
Library: Library:
- The new function suffix_operator_normal_form() implements
transformation of formulas to Suffix Operator Normal Form,
described in [cimatti.06.fmcad].
- "original-classes" is a new named property similar to - "original-classes" is a new named property similar to
"original-states". It maps an each state to an unsigned integer "original-states". It maps an each state to an unsigned integer
such that if two classes are in the same class, they are expected such that if two classes are in the same class, they are expected

View file

@ -214,6 +214,18 @@
doi = {10.1109/DepCoS-RELCOMEX.2009.31} doi = {10.1109/DepCoS-RELCOMEX.2009.31}
} }
@InProceedings{ cimatti.06.fmcad,
author = {Cimatti, Alessandro and Roveri, Marco and Semprini, Simone and
Tonetta, Stefano},
title = {From {PSL} to {NBA}: a Modular Symbolic Encoding},
booktitle = {Proceedings of the 6th conference on Formal Methods in Computer
Aided Design (FMCAD'06)},
pages = {125--133},
year = {2006},
publisher = {IEEE Computer Society},
doi = {10.1109/FMCAD.2006.19}
}
@Article{ cimatti.08.tcad, @Article{ cimatti.08.tcad,
author = {Alessandro Cimatti and Marco Roveri and Stefano Tonetta}, author = {Alessandro Cimatti and Marco Roveri and Stefano Tonetta},
journal = {IEEE Transactions on Computer Aided Design of Integrated journal = {IEEE Transactions on Computer Aided Design of Integrated

View file

@ -94,6 +94,7 @@
#include <spot/tl/nenoform.hh> #include <spot/tl/nenoform.hh>
#include <spot/tl/print.hh> #include <spot/tl/print.hh>
#include <spot/tl/simplify.hh> #include <spot/tl/simplify.hh>
#include <spot/tl/sonf.hh>
#include <spot/tl/unabbrev.hh> #include <spot/tl/unabbrev.hh>
#include <spot/tl/randomltl.hh> #include <spot/tl/randomltl.hh>
#include <spot/tl/length.hh> #include <spot/tl/length.hh>
@ -517,6 +518,7 @@ namespace std {
%template(vectorbdd) vector<bdd>; %template(vectorbdd) vector<bdd>;
%template(aliasvector) vector<pair<string, bdd>>; %template(aliasvector) vector<pair<string, bdd>>;
%template(vectorstring) vector<string>; %template(vectorstring) vector<string>;
%template(pair_formula_vectorstring) pair<spot::formula, vector<string>>;
%template(atomic_prop_set) set<spot::formula>; %template(atomic_prop_set) set<spot::formula>;
%template(relabeling_map) map<spot::formula, spot::formula>; %template(relabeling_map) map<spot::formula, spot::formula>;
} }
@ -577,6 +579,7 @@ namespace std {
%include <spot/tl/contain.hh> %include <spot/tl/contain.hh>
%include <spot/tl/dot.hh> %include <spot/tl/dot.hh>
%include <spot/tl/nenoform.hh> %include <spot/tl/nenoform.hh>
%include <spot/tl/sonf.hh>
%include <spot/tl/print.hh> %include <spot/tl/print.hh>
%include <spot/tl/simplify.hh> %include <spot/tl/simplify.hh>
%include <spot/tl/unabbrev.hh> %include <spot/tl/unabbrev.hh>

View file

@ -44,6 +44,7 @@ tl_HEADERS = \
remove_x.hh \ remove_x.hh \
simplify.hh \ simplify.hh \
snf.hh \ snf.hh \
sonf.hh \
unabbrev.hh unabbrev.hh
noinst_LTLIBRARIES = libtl.la noinst_LTLIBRARIES = libtl.la
@ -68,4 +69,5 @@ libtl_la_SOURCES = \
remove_x.cc \ remove_x.cc \
simplify.cc \ simplify.cc \
snf.cc \ snf.cc \
sonf.cc \
unabbrev.cc unabbrev.cc

185
spot/tl/sonf.cc Normal file
View file

@ -0,0 +1,185 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2021 Laboratoire de Recherche et Developpement de
// l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "config.h"
#include <spot/tl/nenoform.hh>
#include <spot/tl/sonf.hh>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
namespace spot
{
namespace
{
/// Uses `extractor` to extract some parts of the formula and replace them
/// with atomic propositions.
///
/// Returns (f & g1 & g2 & .. & gn) with g1..gn the extracted subformulas.
///
/// `extractor` should be a lambda taking the following parameters as input:
///
/// - `formula` the formula to process
/// - `std::vector<formula>&` the vector that stores extracted subformulas
/// - `auto&&` itself, in case it needs to call itself recursively
/// (formula::map for example)
/// - `bool` a boolean indicating whether the lambda is currently being
/// called at the formula's "root"
/// - `bool` a boolean indicating whether the lambda is currently being
/// called inside a toplevel `and` construct.
///
/// Note that the last 2 boolean arguments can be used as you see fit in
/// your recursive calls, the first one being set to true in the original
/// call, and the second one to false.
///
/// `extractor` should return the new rewritten formula.
///
/// auto sample_extractor = [](formula f,
/// std::vector<formula>& extracted,
/// auto&& extractor,
/// bool top_level,
/// bool in_top_level_and) -> formula
template<typename Ext>
static formula
extract(formula f, Ext extractor)
{
std::vector<formula> extracted;
formula new_f = extractor(f, extracted, extractor, true, false);
extracted.push_back(new_f);
return formula::And(extracted);
}
}
std::pair<formula, std::vector<std::string>>
suffix_operator_normal_form(formula f, const std::string prefix)
{
// SONF can only be applied to formulas in negative normal form
f = negative_normal_form(f);
std::unordered_set<std::string> used_aps;
std::vector<std::string> added_aps;
size_t count = 0;
// identify all used ap names to avoid them when generating new ones
auto ap_indexer = [&used_aps](formula f) noexcept {
if (f.is(op::ap))
{
used_aps.insert(f.ap_name());
return true;
}
return false;
};
f.traverse(ap_indexer);
auto new_ap_name =
[&used_aps, &added_aps, &prefix, &count]() noexcept -> std::string
{
std::string new_name = prefix + std::to_string(count++);
while (used_aps.find(new_name) != used_aps.end())
new_name = prefix + std::to_string(count++);
used_aps.insert(new_name);
added_aps.push_back(new_name);
return new_name;
};
// extracts the SERE part and replaces it with an atomic proposition,
// storing the extracted formula in `extracted` and returning the rewritten
// original formula
auto sonf_extract = [&](formula f,
std::vector<formula>& extracted,
auto&& extractor,
bool top_level,
bool in_top_level_and) noexcept -> formula
{
const auto kind = f.kind();
switch (kind)
{
case op::G:
{
// skip if shape is G(!ap | (regex []-> formula)) and at top level
if ((top_level || in_top_level_and)
&& f[0].is(op::Or) // G(_ | _)
&& f[0][0].is(op::Not) // G(!_ | _)
&& f[0][0][0].is(op::ap) // G(!ap | _)
&& f[0][1].is(op::EConcat, op::UConcat)) // G(!ap | (_ []-> _))
return f;
else
return f.map(extractor, extracted, extractor, false, false);
}
case op::EConcat:
case op::UConcat:
{
// recurse into rhs first (_ []-> rhs)
formula rhs =
f[1].map(extractor, extracted, extractor, false, false);
f = formula::binop(kind, f[0], rhs);
formula ap = formula::ap(new_ap_name());
extracted.push_back(formula::G(formula::Or({formula::Not(ap), f})));
return ap;
}
default:
// tracking if we're in a op::And at the formula root
in_top_level_and = top_level && f.is(op::And);
return f.map(extractor, extracted, extractor,
false, in_top_level_and);
}
};
f = extract(f, sonf_extract);
auto ltl_extract = [&](formula f,
std::vector<formula>& extracted,
auto&& extractor,
[[maybe_unused]]
bool top_level,
[[maybe_unused]]
bool in_top_level_and) noexcept -> formula
{
switch (f.kind())
{
case op::EConcat:
case op::UConcat:
{
formula rhs = f[1];
if (rhs.is(op::ap))
return f;
formula ap = formula::ap(new_ap_name());
extracted.push_back(
formula::G(formula::Or({formula::Not(ap), rhs})));
return formula::binop(f.kind(), f[0], ap);
}
default:
return f.map(extractor, extracted, extractor, false, false);
}
};
f = extract(f, ltl_extract);
return {f, added_aps};
}
}

44
spot/tl/sonf.hh Normal file
View file

@ -0,0 +1,44 @@
// -*- coding: utf-8 -*-
// Copyright (C) 2021 Laboratoire de Recherche et Développement de
// l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#pragma once
#include <string>
#include <utility>
#include <vector>
#include <spot/tl/formula.hh>
namespace spot
{
/// \ingroup tl_rewriting
/// \brief Helper to rewrite a PSL formula in Suffix Operator Normal Form.
///
/// SONF is described in section 4 of \cite cimatti.06.fmcad
///
/// The formula output by this function is guaranteed to be in Negative Normal
/// Form.
///
/// \param f the PSL formula to rewrite
/// \param prefix the prefix to use to name newly introduced aps
/// \return a pair with the rewritten formula, and a vector containing the
/// names of newly introduced aps
SPOT_API std::pair<formula, std::vector<std::string>>
suffix_operator_normal_form(formula f, const std::string prefix);
}

View file

@ -445,6 +445,7 @@ TESTS_python = \
python/setxor.py \ python/setxor.py \
python/simplacc.py \ python/simplacc.py \
python/simstate.py \ python/simstate.py \
python/sonf.py \
python/split.py \ python/split.py \
python/streett_totgba.py \ python/streett_totgba.py \
python/streett_totgba2.py \ python/streett_totgba2.py \

View file

@ -976,6 +976,62 @@
"print(ap) # print as a string\n", "print(ap) # print as a string\n",
"display(ap) # LaTeX-style, for notebooks" "display(ap) # LaTeX-style, for notebooks"
] ]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Converting to Suffix Operator Normal Form:"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/latex": [
"$\\mathsf{G} (\\{x^{\\star}\\}\\mathrel{\\Box\\kern-1.7pt\\raise.4pt\\hbox{$\\mathord{\\rightarrow}$}} \\mathsf{F} a)$"
],
"text/plain": [
"spot.formula(\"G({x[*]}[]-> Fa)\")"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/latex": [
"$\\mathsf{G} \\mathit{sonf\\_}_{0} \\land \\mathsf{G} (\\lnot \\mathit{sonf\\_}_{1} \\lor \\mathsf{F} a) \\land \\mathsf{G} (\\lnot \\mathit{sonf\\_}_{0} \\lor (\\{x^{\\star}\\}\\mathrel{\\Box\\kern-1.7pt\\raise.4pt\\hbox{$\\mathord{\\rightarrow}$}} \\mathit{sonf\\_}_{1}))$"
],
"text/plain": [
"spot.formula(\"Gsonf_0 & G(!sonf_1 | Fa) & G(!sonf_0 | ({x[*]}[]-> sonf_1))\")"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"('sonf_0', 'sonf_1')"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"f = spot.formula('G({x*} []-> Fa)')\n",
"display(f)\n",
"\n",
"# In addition to the formula, returns a list of newly introduced APs\n",
"f, aps = spot.suffix_operator_normal_form(f, 'sonf_')\n",
"display(f)\n",
"display(aps)"
]
} }
], ],
"metadata": { "metadata": {

41
tests/python/sonf.py Normal file
View file

@ -0,0 +1,41 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2020 Laboratoire de Recherche et Développement de l'Epita
# (LRDE).
#
# This file is part of Spot, a model checking library.
#
# Spot is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Spot is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import spot
formulas = """\
{x[*]}[]-> F({y[*]}<>-> GFz)
<>(({{p12}[*0..3]}[]-> ((p9) || (!(p17)))) V ((true) U (p17)))
{{true} || {[*0]}}[]-> (false)
{{p14} & {{p0}[*]}}[]-> (p11)
{{{!{p6}} -> {!{p3}}}[*]}[]-> ((p3)V((p3) || ((X((false))) && ((p2)V(p18)))))
"""
for f1 in formulas.splitlines():
f1 = spot.formula(f1)
a1 = spot.translate(f1)
f2, aps = spot.suffix_operator_normal_form(f1, 'sonf_')
a2 = spot.translate(f2)
rm = spot.remove_ap()
for ap in aps:
rm.add_ap(ap)
a2 = rm.strip(a2)
assert(spot.are_equivalent(a1, a2))