ltlfilt: add a --save-part-file option

* bin/ltlfilt.cc: Add support for --save-part-file.
* NEWS, doc/org/ltlfilt.org: Mention it.
* tests/core/ltlfilt.test: Test it.
This commit is contained in:
Alexandre Duret-Lutz 2025-02-14 14:27:48 +01:00
parent b1b06ef7bd
commit 00456e5211
4 changed files with 91 additions and 7 deletions

4
NEWS
View file

@ -37,6 +37,10 @@ New in spot 2.12.2.dev (not yet released)
be inferred from their name. (This suspports a --part-file option be inferred from their name. (This suspports a --part-file option
as well.) as well.)
- ltlfilt learned --save-part-file[=FILENAME] option that can be
used to create partition files suitable for many synthesis tools
(including ltlsynt).
- genltl learned --lily-patterns to generate the example LTL - genltl learned --lily-patterns to generate the example LTL
synthesis specifications from Lily 1.0.2. Those come with input synthesis specifications from Lily 1.0.2. Those come with input
and output atomic proposition rewriten in the form "iNN" or "oNN", and output atomic proposition rewriten in the form "iNN" or "oNN",

View file

@ -103,6 +103,7 @@ enum {
OPT_REMOVE_WM, OPT_REMOVE_WM,
OPT_REMOVE_X, OPT_REMOVE_X,
OPT_SAFETY, OPT_SAFETY,
OPT_SAVE_PART_FILE,
OPT_SIGMA2, OPT_SIGMA2,
OPT_SIZE, OPT_SIZE,
OPT_SIZE_MAX, OPT_SIZE_MAX,
@ -184,13 +185,18 @@ static const argp_option options[] =
" proposition", 0 }, " proposition", 0 },
{ "ins", OPT_INS, "PROPS", 0, { "ins", OPT_INS, "PROPS", 0,
"comma-separated list of input atomic propositions to use with " "comma-separated list of input atomic propositions to use with "
"--relabel=io, interpreted as a regex if enclosed in slashes", 0 }, "--relabel=io or --save-part-file, interpreted as a regex if enclosed "
"in slashes", 0 },
{ "outs", OPT_OUTS, "PROPS", 0, { "outs", OPT_OUTS, "PROPS", 0,
"comma-separated list of output atomic propositions to use with " "comma-separated list of output atomic propositions to use with "
"--relabel=io, interpreted as a regex if enclosed in slashes", 0 }, "--relabel=io or --save-part-file, interpreted as a regex if "
"enclosed in slashes", 0 },
{ "part-file", OPT_PART_FILE, "FILENAME", 0, { "part-file", OPT_PART_FILE, "FILENAME", 0,
"file containing the partition of atomic propositions to use with " "file containing the partition of atomic propositions to use with "
"--relabel=io", 0 }, "--relabel=io", 0 },
{ "save-part-file", OPT_SAVE_PART_FILE, "FILENAME", OPTION_ARG_OPTIONAL,
"file containing the partition of atomic propositions, "
"readable by --part-file", 0 },
DECLARE_OPT_R, DECLARE_OPT_R,
LEVEL_DOC(4), LEVEL_DOC(4),
/**************************************************/ /**************************************************/
@ -377,6 +383,7 @@ static struct opt_t
{ {
spot::bdd_dict_ptr dict = spot::make_bdd_dict(); spot::bdd_dict_ptr dict = spot::make_bdd_dict();
spot::exclusive_ap excl_ap; spot::exclusive_ap excl_ap;
std::unique_ptr<output_file> output_part = nullptr;
std::unique_ptr<output_file> output_define = nullptr; std::unique_ptr<output_file> output_define = nullptr;
std::unique_ptr<output_file> output_sonf = nullptr; std::unique_ptr<output_file> output_sonf = nullptr;
spot::formula implied_by = nullptr; spot::formula implied_by = nullptr;
@ -596,6 +603,9 @@ parse_opt(int key, char* arg, struct argp_state*)
case OPT_SAFETY: case OPT_SAFETY:
safety = obligation = true; safety = obligation = true;
break; break;
case OPT_SAVE_PART_FILE:
opt->output_part.reset(new output_file(arg ? arg : "-"));
break;
case OPT_SIZE: case OPT_SIZE:
size = parse_range(arg, 0, std::numeric_limits<int>::max()); size = parse_range(arg, 0, std::numeric_limits<int>::max());
break; break;
@ -954,6 +964,40 @@ namespace
oldname, filename, oldname, filename,
std::to_string(linenum).c_str()) << ")\n"; std::to_string(linenum).c_str()) << ")\n";
} }
if (opt->output_part
&& output_format != count_output
&& output_format != quiet_output)
{
std::vector<spot::formula> ins;
std::vector<spot::formula> outs;
spot::atomic_prop_set* s = atomic_prop_collect(f);
for (spot::formula ap: *s)
{
spot::formula apo = ap;
if (auto it = relmap.find(ap); it != relmap.end())
apo = it->second;
if (is_output(apo.ap_name(), filename, linenum))
outs.push_back(ap);
else
ins.push_back(ap);
}
delete s;
auto& os = opt->output_part->ostream();
if (!ins.empty())
{
os << ".inputs";
for (const auto& ap: ins)
os << ' ' << str_psl(ap);
os << '\n';
}
if (!outs.empty())
{
os << ".outputs";
for (const auto& ap: outs)
os << ' ' << str_psl(ap);
os << '\n';
}
}
one_match = true; one_match = true;
output_formula_checked(f, &timer, filename, linenum, output_formula_checked(f, &timer, filename, linenum,
match_count, prefix, suffix); match_count, prefix, suffix);
@ -982,7 +1026,7 @@ main(int argc, char** argv)
if (jobs.empty()) if (jobs.empty())
jobs.emplace_back("-", job_type::LTL_FILENAME); jobs.emplace_back("-", job_type::LTL_FILENAME);
if (relabeling == IOApRelabeling) if (relabeling == IOApRelabeling || opt->output_part)
process_io_options(); process_io_options();
if (boolean_to_isop && simplification_level == 0) if (boolean_to_isop && simplification_level == 0)

View file

@ -301,14 +301,18 @@ ltldo ltl3ba -f '"proc@loc1" U "proc@loc2"' --spin
This case also relabels the formula before calling =ltl3ba=, and it This case also relabels the formula before calling =ltl3ba=, and it
then renames all the atomic propositions in the output. then renames all the atomic propositions in the output.
An example showing how to use the =--from-ltlf= option is on [[file:tut12.org][a
separate page]].
* I/O-partitioned formulas
A special relabeling mode related to LTL synthesis is =--relabel=io=. A special relabeling mode related to LTL synthesis is =--relabel=io=.
In LTL synthesis (see [[file:ltlsynt.org][=ltlsynt=]]), atomic propositions are partitioned In LTL synthesis (see [[file:ltlsynt.org][=ltlsynt=]]), atomic propositions are partitioned
in two sets: the /input/ propositions represent choices from the in two sets: the /input/ propositions represent choices from the
environment, while /output/ proposition represent choices by the environment, while /output/ propositions represent choices by the
controller to be synthesized. For instance controller to be synthesized. For instance
=G(req -> Fack) & G(go -> Fgrant)= =G(req -> Fack) & G(go -> Fgrant)=
represents could be a specification where =req= and =go= are inputs, could be a specification where =req= and =go= are inputs,
while =ack= and =grant= are outputs. Tool such as =ltlsynt= need while =ack= and =grant= are outputs. Tool such as =ltlsynt= need
to be told using options such as =--ins= or =--outs= which atomic to be told using options such as =--ins= or =--outs= which atomic
propositions are input or output. Often these atomic propositions propositions are input or output. Often these atomic propositions
@ -331,8 +335,24 @@ when these two options are missing the convention is that anything
starting with =i= is an input, and anything starting with =o= is an starting with =i= is an input, and anything starting with =o= is an
output. output.
An example showing how to use the =--from-ltlf= option is on [[file:tut12.org][a =ltlfilt= can also be instructed to create a partition file (usually
separate page]]. named =*.part=) that can be used by synthesis tools.
#+BEGIN_SRC sh
ltlfilt -f 'G(req -> Fack) & G(go -> Fgrant)' --relabel=io \
--ins=req,go --save-part=out.part
#+END_SRC
#+RESULTS:
: G(i1 -> Fo0) & G(i0 -> Fo1)
In addition to the relabeling, this also created a file =out.part=
containing the following:
#+BEGIN_SRC sh :exports results
cat out.part
#+END_SRC
#+RESULTS:
: .inputs i0 i1
: .outputs o0 o1
* Filtering * Filtering

View file

@ -872,3 +872,19 @@ diff expected out
# would stick to the rest of the file unless overridden. # would stick to the rest of the file unless overridden.
run 2 ltlfilt -Ffile/1 -Ffile run 2 ltlfilt -Ffile/1 -Ffile
: :
run 0 ltlfilt -f 'i1 U o2 U i3' --save-part=p.part
cat > exp.part<<EOF
.inputs i1 i3
.outputs o2
EOF
diff exp.part p.part
run 0 ltlfilt -f 'a U b U c' --ins=a --relabel=io --save-part >out
cat > exp<<EOF
.inputs i0
.outputs o0 o1
i0 U (o0 U o1)
EOF
diff exp out