Fixes #539. * AUTHORS: Update by indicating the status of each contributor. * Makefile.am, bench/Makefile.am, bench/dtgbasat/Makefile.am, bench/dtgbasat/gen.py, bench/emptchk/Makefile.am, bench/emptchk/defs.in, bench/ltl2tgba/Makefile.am, bench/ltl2tgba/defs.in, bench/ltl2tgba/sum.py, bench/ltlclasses/Makefile.am, bench/ltlcounter/Makefile.am, bench/spin13/Makefile.am, bench/stutter/Makefile.am, bench/stutter/stutter_invariance_formulas.cc, bench/stutter/stutter_invariance_randomgraph.cc, bench/wdba/Makefile.am, bin/Makefile.am, bin/autcross.cc, bin/autfilt.cc, bin/common_aoutput.cc, bin/common_aoutput.hh, bin/common_color.cc, bin/common_color.hh, bin/common_conv.cc, bin/common_conv.hh, bin/common_cout.cc, bin/common_cout.hh, bin/common_file.cc, bin/common_file.hh, bin/common_finput.cc, bin/common_finput.hh, bin/common_hoaread.cc, bin/common_hoaread.hh, bin/common_output.cc, bin/common_output.hh, bin/common_post.cc, bin/common_post.hh, bin/common_r.cc, bin/common_r.hh, bin/common_range.cc, bin/common_range.hh, bin/common_setup.cc, bin/common_setup.hh, bin/common_sys.hh, bin/common_trans.cc, bin/common_trans.hh, bin/dstar2tgba.cc, bin/genaut.cc, bin/genltl.cc, bin/ltl2tgba.cc, bin/ltl2tgta.cc, bin/ltlcross.cc, bin/ltldo.cc, bin/ltlfilt.cc, bin/ltlgrind.cc, bin/ltlsynt.cc, bin/man/Makefile.am, bin/options.py, bin/randaut.cc, bin/randltl.cc, bin/spot-x.cc, bin/spot.cc, configure.ac, debian/copyright, doc/Makefile.am, doc/tl/Makefile.am, elisp/Makefile.am, python/Makefile.am, python/buddy.i, python/spot/__init__.py, python/spot/aux_.py, python/spot/gen.i, python/spot/impl.i, python/spot/jupyter.py, python/spot/ltsmin.i, spot/Makefile.am, spot/gen/Makefile.am, spot/gen/automata.cc, spot/gen/automata.hh, spot/gen/formulas.cc, spot/gen/formulas.hh, spot/graph/Makefile.am, spot/graph/graph.hh, spot/graph/ngraph.hh, spot/kripke/Makefile.am, spot/kripke/fairkripke.cc, spot/kripke/fairkripke.hh, spot/kripke/fwd.hh, spot/kripke/kripke.cc, spot/kripke/kripke.hh, spot/kripke/kripkegraph.hh, spot/ltsmin/Makefile.am, spot/ltsmin/ltsmin.cc, spot/ltsmin/ltsmin.hh, spot/ltsmin/spins_interface.cc, spot/ltsmin/spins_interface.hh, spot/ltsmin/spins_kripke.hh, spot/ltsmin/spins_kripke.hxx, spot/mc/Makefile.am, spot/mc/bloemen.hh, spot/mc/bloemen_ec.hh, spot/mc/cndfs.hh, spot/mc/deadlock.hh, spot/mc/intersect.hh, spot/mc/lpar13.hh, spot/mc/mc.hh, spot/mc/mc_instanciator.hh, spot/mc/unionfind.cc, spot/mc/unionfind.hh, spot/mc/utils.hh, spot/misc/Makefile.am, spot/misc/bareword.cc, spot/misc/bareword.hh, spot/misc/bddlt.hh, spot/misc/bitset.cc, spot/misc/bitset.hh, spot/misc/bitvect.cc, spot/misc/bitvect.hh, spot/misc/casts.hh, spot/misc/clz.hh, spot/misc/common.hh, spot/misc/escape.cc, spot/misc/escape.hh, spot/misc/fixpool.hh, spot/misc/formater.cc, spot/misc/formater.hh, spot/misc/hash.hh, spot/misc/hashfunc.hh, spot/misc/intvcmp2.cc, spot/misc/intvcmp2.hh, spot/misc/intvcomp.cc, spot/misc/intvcomp.hh, spot/misc/ltstr.hh, spot/misc/memusage.cc, spot/misc/memusage.hh, spot/misc/minato.cc, spot/misc/minato.hh, spot/misc/mspool.hh, spot/misc/optionmap.cc, spot/misc/optionmap.hh, spot/misc/random.cc, spot/misc/random.hh, spot/misc/satsolver.cc, spot/misc/satsolver.hh, spot/misc/timer.cc, spot/misc/timer.hh, spot/misc/tmpfile.cc, spot/misc/tmpfile.hh, spot/misc/trival.hh, spot/misc/version.cc, spot/misc/version.hh, spot/parseaut/Makefile.am, spot/parseaut/fmterror.cc, spot/parseaut/parseaut.yy, spot/parseaut/parsedecl.hh, spot/parseaut/public.hh, spot/parseaut/scanaut.ll, spot/parsetl/Makefile.am, spot/parsetl/fmterror.cc, spot/parsetl/parsedecl.hh, spot/parsetl/parsetl.yy, spot/parsetl/scantl.ll, spot/priv/Makefile.am, spot/priv/accmap.hh, spot/priv/bddalloc.cc, spot/priv/bddalloc.hh, spot/priv/freelist.cc, spot/priv/freelist.hh, spot/priv/partitioned_relabel.cc, spot/priv/partitioned_relabel.hh, spot/priv/satcommon.cc, spot/priv/satcommon.hh, spot/priv/trim.cc, spot/priv/trim.hh, spot/priv/weight.cc, spot/priv/weight.hh, spot/ta/Makefile.am, spot/ta/ta.cc, spot/ta/ta.hh, spot/ta/taexplicit.cc, spot/ta/taexplicit.hh, spot/ta/taproduct.cc, spot/ta/taproduct.hh, spot/ta/tgta.hh, spot/ta/tgtaexplicit.cc, spot/ta/tgtaexplicit.hh, spot/ta/tgtaproduct.cc, spot/ta/tgtaproduct.hh, spot/taalgos/Makefile.am, spot/taalgos/dot.cc, spot/taalgos/dot.hh, spot/taalgos/emptinessta.cc, spot/taalgos/emptinessta.hh, spot/taalgos/minimize.cc, spot/taalgos/minimize.hh, spot/taalgos/reachiter.cc, spot/taalgos/reachiter.hh, spot/taalgos/statessetbuilder.cc, spot/taalgos/statessetbuilder.hh, spot/taalgos/stats.cc, spot/taalgos/stats.hh, spot/taalgos/tgba2ta.cc, spot/taalgos/tgba2ta.hh, spot/tl/Makefile.am, spot/tl/apcollect.cc, spot/tl/apcollect.hh, spot/tl/contain.cc, spot/tl/contain.hh, spot/tl/declenv.cc, spot/tl/declenv.hh, spot/tl/defaultenv.cc, spot/tl/defaultenv.hh, spot/tl/dot.cc, spot/tl/dot.hh, spot/tl/environment.hh, spot/tl/exclusive.cc, spot/tl/exclusive.hh, spot/tl/formula.cc, spot/tl/formula.hh, spot/tl/hierarchy.cc, spot/tl/hierarchy.hh, spot/tl/length.cc, spot/tl/length.hh, spot/tl/ltlf.cc, spot/tl/ltlf.hh, spot/tl/mark.cc, spot/tl/mark.hh, spot/tl/mutation.cc, spot/tl/mutation.hh, spot/tl/nenoform.cc, spot/tl/nenoform.hh, spot/tl/parse.hh, spot/tl/print.cc, spot/tl/print.hh, spot/tl/randomltl.cc, spot/tl/randomltl.hh, spot/tl/relabel.cc, spot/tl/relabel.hh, spot/tl/remove_x.cc, spot/tl/remove_x.hh, spot/tl/simplify.cc, spot/tl/simplify.hh, spot/tl/snf.cc, spot/tl/snf.hh, spot/tl/sonf.cc, spot/tl/sonf.hh, spot/tl/unabbrev.cc, spot/tl/unabbrev.hh, spot/twa/Makefile.am, spot/twa/acc.cc, spot/twa/acc.hh, spot/twa/bdddict.cc, spot/twa/bdddict.hh, spot/twa/bddprint.cc, spot/twa/bddprint.hh, spot/twa/formula2bdd.cc, spot/twa/formula2bdd.hh, spot/twa/fwd.hh, spot/twa/taatgba.cc, spot/twa/taatgba.hh, spot/twa/twa.cc, spot/twa/twa.hh, spot/twa/twagraph.cc, spot/twa/twagraph.hh, spot/twa/twaproduct.cc, spot/twa/twaproduct.hh, spot/twaalgos/Makefile.am, spot/twaalgos/aiger.cc, spot/twaalgos/aiger.hh, spot/twaalgos/alternation.cc, spot/twaalgos/alternation.hh, spot/twaalgos/are_isomorphic.cc, spot/twaalgos/are_isomorphic.hh, spot/twaalgos/bfssteps.cc, spot/twaalgos/bfssteps.hh, spot/twaalgos/canonicalize.cc, spot/twaalgos/canonicalize.hh, spot/twaalgos/cleanacc.cc, spot/twaalgos/cleanacc.hh, spot/twaalgos/cobuchi.cc, spot/twaalgos/cobuchi.hh, spot/twaalgos/complement.cc, spot/twaalgos/complement.hh, spot/twaalgos/complete.cc, spot/twaalgos/complete.hh, spot/twaalgos/compsusp.cc, spot/twaalgos/compsusp.hh, spot/twaalgos/contains.cc, spot/twaalgos/contains.hh, spot/twaalgos/copy.hh, spot/twaalgos/couvreurnew.cc, spot/twaalgos/couvreurnew.hh, spot/twaalgos/cycles.cc, spot/twaalgos/cycles.hh, spot/twaalgos/dbranch.cc, spot/twaalgos/dbranch.hh, spot/twaalgos/degen.cc, spot/twaalgos/degen.hh, spot/twaalgos/determinize.cc, spot/twaalgos/determinize.hh, spot/twaalgos/dot.cc, spot/twaalgos/dot.hh, spot/twaalgos/dtbasat.cc, spot/twaalgos/dtbasat.hh, spot/twaalgos/dtwasat.cc, spot/twaalgos/dtwasat.hh, spot/twaalgos/dualize.cc, spot/twaalgos/dualize.hh, spot/twaalgos/emptiness.cc, spot/twaalgos/emptiness.hh, spot/twaalgos/emptiness_stats.hh, spot/twaalgos/forq_contains.cc, spot/twaalgos/forq_contains.hh, spot/twaalgos/game.cc, spot/twaalgos/game.hh, spot/twaalgos/genem.cc, spot/twaalgos/genem.hh, spot/twaalgos/gfguarantee.cc, spot/twaalgos/gfguarantee.hh, spot/twaalgos/gtec/Makefile.am, spot/twaalgos/gtec/ce.cc, spot/twaalgos/gtec/ce.hh, spot/twaalgos/gtec/gtec.cc, spot/twaalgos/gtec/gtec.hh, spot/twaalgos/gtec/sccstack.cc, spot/twaalgos/gtec/sccstack.hh, spot/twaalgos/gtec/status.cc, spot/twaalgos/gtec/status.hh, spot/twaalgos/gv04.cc, spot/twaalgos/gv04.hh, spot/twaalgos/hoa.cc, spot/twaalgos/hoa.hh, spot/twaalgos/iscolored.cc, spot/twaalgos/iscolored.hh, spot/twaalgos/isdet.cc, spot/twaalgos/isdet.hh, spot/twaalgos/isunamb.cc, spot/twaalgos/isunamb.hh, spot/twaalgos/isweakscc.cc, spot/twaalgos/isweakscc.hh, spot/twaalgos/langmap.cc, spot/twaalgos/langmap.hh, spot/twaalgos/lbtt.cc, spot/twaalgos/lbtt.hh, spot/twaalgos/ltl2taa.cc, spot/twaalgos/ltl2taa.hh, spot/twaalgos/ltl2tgba_fm.cc, spot/twaalgos/ltl2tgba_fm.hh, spot/twaalgos/magic.cc, spot/twaalgos/magic.hh, spot/twaalgos/mask.cc, spot/twaalgos/mask.hh, spot/twaalgos/mealy_machine.cc, spot/twaalgos/mealy_machine.hh, spot/twaalgos/minimize.cc, spot/twaalgos/minimize.hh, spot/twaalgos/ndfs_result.hxx, spot/twaalgos/neverclaim.cc, spot/twaalgos/neverclaim.hh, spot/twaalgos/parity.cc, spot/twaalgos/parity.hh, spot/twaalgos/postproc.cc, spot/twaalgos/postproc.hh, spot/twaalgos/powerset.cc, spot/twaalgos/powerset.hh, spot/twaalgos/product.cc, spot/twaalgos/product.hh, spot/twaalgos/randomgraph.cc, spot/twaalgos/randomgraph.hh, spot/twaalgos/randomize.cc, spot/twaalgos/randomize.hh, spot/twaalgos/reachiter.cc, spot/twaalgos/reachiter.hh, spot/twaalgos/relabel.cc, spot/twaalgos/relabel.hh, spot/twaalgos/remfin.cc, spot/twaalgos/remfin.hh, spot/twaalgos/remprop.cc, spot/twaalgos/remprop.hh, spot/twaalgos/sbacc.cc, spot/twaalgos/sbacc.hh, spot/twaalgos/sccfilter.cc, spot/twaalgos/sccfilter.hh, spot/twaalgos/sccinfo.cc, spot/twaalgos/sccinfo.hh, spot/twaalgos/se05.cc, spot/twaalgos/se05.hh, spot/twaalgos/sepsets.cc, spot/twaalgos/sepsets.hh, spot/twaalgos/simulation.cc, spot/twaalgos/simulation.hh, spot/twaalgos/split.cc, spot/twaalgos/split.hh, spot/twaalgos/stats.cc, spot/twaalgos/stats.hh, spot/twaalgos/strength.cc, spot/twaalgos/strength.hh, spot/twaalgos/stripacc.cc, spot/twaalgos/stripacc.hh, spot/twaalgos/stutter.cc, spot/twaalgos/stutter.hh, spot/twaalgos/sum.cc, spot/twaalgos/sum.hh, spot/twaalgos/synthesis.cc, spot/twaalgos/synthesis.hh, spot/twaalgos/tau03.cc, spot/twaalgos/tau03.hh, spot/twaalgos/tau03opt.cc, spot/twaalgos/tau03opt.hh, spot/twaalgos/toparity.cc, spot/twaalgos/toparity.hh, spot/twaalgos/totgba.cc, spot/twaalgos/totgba.hh, spot/twaalgos/toweak.cc, spot/twaalgos/toweak.hh, spot/twaalgos/translate.cc, spot/twaalgos/translate.hh, spot/twaalgos/word.cc, spot/twaalgos/word.hh, spot/twaalgos/zlktree.cc, spot/twaalgos/zlktree.hh, spot/twacube/Makefile.am, spot/twacube/cube.cc, spot/twacube/cube.hh, spot/twacube/fwd.hh, spot/twacube/twacube.cc, spot/twacube/twacube.hh, spot/twacube_algos/Makefile.am, spot/twacube_algos/convert.cc, spot/twacube_algos/convert.hh, tests/Makefile.am, tests/core/385.test, tests/core/500.test, tests/core/521.test, tests/core/522.test, tests/core/acc.cc, tests/core/acc.test, tests/core/acc2.test, tests/core/acc_word.test, tests/core/accsimpl.test, tests/core/alternating.test, tests/core/autcross.test, tests/core/autcross2.test, tests/core/autcross3.test, tests/core/autcross4.test, tests/core/autcross5.test, tests/core/babiak.test, tests/core/bare.test, tests/core/basimul.test, tests/core/bdd.test, tests/core/bdddict.cc, tests/core/bdddict.test, tests/core/bitvect.cc, tests/core/bitvect.test, tests/core/bricks.cc, tests/core/bricks.test, tests/core/checkpsl.cc, tests/core/checkta.cc, tests/core/complement.test, tests/core/complementation.test, tests/core/complete.test, tests/core/consterm.cc, tests/core/consterm.test, tests/core/cube.cc, tests/core/cube.test, tests/core/cycles.test, tests/core/dbacomp.test, tests/core/dca.test, tests/core/dca2.test, tests/core/defs.in, tests/core/degendet.test, tests/core/degenid.test, tests/core/degenlskip.test, tests/core/degenscc.test, tests/core/det.test, tests/core/dfs.test, tests/core/dnfstreett.test, tests/core/dot2tex.test, tests/core/dra2dba.test, tests/core/dstar.test, tests/core/dualize.test, tests/core/dupexp.test, tests/core/emptchk.cc, tests/core/emptchk.test, tests/core/emptchke.test, tests/core/emptchkr.test, tests/core/equals.test, tests/core/equalsf.cc, tests/core/eventuniv.test, tests/core/exclusive-ltl.test, tests/core/exclusive-tgba.test, tests/core/explpro2.test, tests/core/explpro3.test, tests/core/explpro4.test, tests/core/explprod.test, tests/core/explsum.test, tests/core/format.test, tests/core/full.test, tests/core/gamehoa.test, tests/core/genaut.test, tests/core/genltl.test, tests/core/gragsa.test, tests/core/graph.cc, tests/core/graph.test, tests/core/hierarchy.test, tests/core/highlightstate.test, tests/core/ikwiad.cc, tests/core/included.test, tests/core/intvcmp2.cc, tests/core/intvcomp.cc, tests/core/intvcomp.test, tests/core/isomorph.test, tests/core/isop.test, tests/core/kind.cc, tests/core/kind.test, tests/core/kripke.test, tests/core/kripkecat.cc, tests/core/latex.test, tests/core/lbt.test, tests/core/lbttparse.test, tests/core/length.cc, tests/core/length.test, tests/core/lenient.test, tests/core/ltl2dstar.test, tests/core/ltl2dstar2.test, tests/core/ltl2dstar3.test, tests/core/ltl2dstar4.test, tests/core/ltl2neverclaim-lbtt.test, tests/core/ltl2neverclaim.test, tests/core/ltl2ta.test, tests/core/ltl2ta2.test, tests/core/ltl2tgba.test, tests/core/ltl2tgba2.test, tests/core/ltl3ba.test, tests/core/ltl3dra.test, tests/core/ltlcounter.test, tests/core/ltlcross.test, tests/core/ltlcross2.test, tests/core/ltlcross3.test, tests/core/ltlcross4.test, tests/core/ltlcross5.test, tests/core/ltlcross6.test, tests/core/ltlcrossce.test, tests/core/ltlcrossce2.test, tests/core/ltlcrossgrind.test, tests/core/ltldo.test, tests/core/ltldo2.test, tests/core/ltlf.test, tests/core/ltlfilt.test, tests/core/ltlgrind.test, tests/core/ltlrel.cc, tests/core/ltlrel.test, tests/core/ltlsynt-pgame.test, tests/core/ltlsynt.test, tests/core/ltlsynt2.test, tests/core/lunabbrev.test, tests/core/maskacc.test, tests/core/maskkeep.test, tests/core/mempool.cc, tests/core/mempool.test, tests/core/minterm.cc, tests/core/minterm.test, tests/core/minusx.test, tests/core/monitor.test, tests/core/nenoform.test, tests/core/neverclaimread.test, tests/core/ngraph.cc, tests/core/ngraph.test, tests/core/nondet.test, tests/core/obligation.test, tests/core/optba.test, tests/core/parity.cc, tests/core/parity.test, tests/core/parity2.test, tests/core/parse.test, tests/core/parseaut.test, tests/core/parseerr.test, tests/core/pdegen.test, tests/core/pgsolver.test, tests/core/prodchain.test, tests/core/prodor.test, tests/core/rabin2parity.test, tests/core/rand.test, tests/core/randaut.test, tests/core/randomize.test, tests/core/randpsl.test, tests/core/randtgba.cc, tests/core/randtgba.test, tests/core/readltl.cc, tests/core/readsave.test, tests/core/reduc.cc, tests/core/reduc.test, tests/core/reduc0.test, tests/core/reduccmp.test, tests/core/reducpsl.test, tests/core/remfin.test, tests/core/remove_x.test, tests/core/remprop.test, tests/core/renault.test, tests/core/safra.cc, tests/core/safra.test, tests/core/satmin.test, tests/core/satmin2.test, tests/core/satmin3.test, tests/core/sbacc.test, tests/core/scc.test, tests/core/sccdot.test, tests/core/sccif.cc, tests/core/sccif.test, tests/core/sccsimpl.test, tests/core/semidet.test, tests/core/sepsets.test, tests/core/serial.test, tests/core/sim2.test, tests/core/sim3.test, tests/core/sonf.test, tests/core/split.test, tests/core/spotlbtt.test, tests/core/spotlbtt2.test, tests/core/streett.test, tests/core/strength.test, tests/core/stutter-ltl.test, tests/core/stutter-tgba.test, tests/core/sugar.test, tests/core/syfco.test, tests/core/syntimpl.cc, tests/core/syntimpl.test, tests/core/taatgba.cc, tests/core/taatgba.test, tests/core/tgbagraph.test, tests/core/tostring.cc, tests/core/tostring.test, tests/core/tripprod.test, tests/core/trival.cc, tests/core/trival.test, tests/core/tunabbrev.test, tests/core/tunenoform.test, tests/core/twacube.cc, tests/core/twacube.test, tests/core/twagraph.cc, tests/core/unabbrevwm.test, tests/core/unambig.test, tests/core/unambig2.test, tests/core/uniq.test, tests/core/utf8.test, tests/core/uwrm.test, tests/core/wdba.test, tests/core/wdba2.test, tests/ltsmin/check.test, tests/ltsmin/check2.test, tests/ltsmin/check3.test, tests/ltsmin/finite.test, tests/ltsmin/finite2.test, tests/ltsmin/finite3.test, tests/ltsmin/kripke.test, tests/ltsmin/modelcheck.cc, tests/ltsmin/testconvert.cc, tests/ltsmin/testconvert.test, tests/python/298.py, tests/python/341.py, tests/python/471.py, tests/python/acc.py, tests/python/accparse2.py, tests/python/aiger.py, tests/python/alarm.py, tests/python/aliases.py, tests/python/alternating.py, tests/python/bdddict.py, tests/python/bdditer.py, tests/python/bddnqueen.py, tests/python/bugdet.py, tests/python/complement_semidet.py, tests/python/dbranch.py, tests/python/declenv.py, tests/python/decompose_scc.py, tests/python/det.py, tests/python/dualize.py, tests/python/ecfalse.py, tests/python/except.py, tests/python/forq_contains.py, tests/python/game.py, tests/python/gen.py, tests/python/genem.py, tests/python/implies.py, tests/python/interdep.py, tests/python/intrun.py, tests/python/kripke.py, tests/python/langmap.py, tests/python/ltl2tgba.py, tests/python/ltl2tgba.test, tests/python/ltlf.py, tests/python/ltlparse.py, tests/python/ltlsimple.py, tests/python/mealy.py, tests/python/merge.py, tests/python/mergedge.py, tests/python/minato.py, tests/python/misc-ec.py, tests/python/optionmap.py, tests/python/origstate.py, tests/python/otfcrash.py, tests/python/parity.py, tests/python/parsetgba.py, tests/python/pdegen.py, tests/python/powerset.py, tests/python/prodexpt.py, tests/python/randgen.py, tests/python/relabel.py, tests/python/remfin.py, tests/python/removeap.py, tests/python/rs_like.py, tests/python/satmin.py, tests/python/sbacc.py, tests/python/sccfilter.py, tests/python/sccinfo.py, tests/python/sccsplit.py, tests/python/semidet.py, tests/python/setacc.py, tests/python/setxor.py, tests/python/simplacc.py, tests/python/simstate.py, tests/python/sonf.py, tests/python/split.py, tests/python/splitedge.py, tests/python/streett_totgba.py, tests/python/streett_totgba2.py, tests/python/stutter.py, tests/python/sum.py, tests/python/synthesis.py, tests/python/toparity.py, tests/python/toweak.py, tests/python/tra2tba.py, tests/python/trival.py, tests/python/twagraph.py, tests/python/zlktree.py, tests/run.in, tests/sanity/80columns.test, tests/sanity/bin.test, tests/sanity/getenv.test, tests/sanity/includes.test, tests/sanity/ipynb.pl, tests/sanity/namedprop.test, tests/sanity/private.test, tests/sanity/readme.pl, tests/sanity/style.test, tools/man2html.pl: Update all copyright headers.
2941 lines
98 KiB
C++
2941 lines
98 KiB
C++
// -*- coding: utf-8 -*-
|
||
// Copyright (C) by the Spot authors, see the AUTHORS file for details.
|
||
//
|
||
// 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/priv/robin_hood.hh>
|
||
#include <spot/twaalgos/cleanacc.hh>
|
||
#include <spot/twaalgos/degen.hh>
|
||
#include <spot/twaalgos/genem.hh>
|
||
#include <spot/twaalgos/parity.hh>
|
||
#include <spot/twaalgos/remfin.hh>
|
||
#include <spot/twaalgos/toparity.hh>
|
||
#include <spot/twaalgos/totgba.hh>
|
||
#include <spot/twaalgos/zlktree.hh>
|
||
|
||
#include <algorithm>
|
||
#include <deque>
|
||
|
||
namespace std
|
||
{
|
||
template <typename T>
|
||
inline void hash_combine(size_t &seed, T const &v)
|
||
{
|
||
seed ^= std::hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||
}
|
||
|
||
template <typename T>
|
||
struct hash<vector<T>>
|
||
{
|
||
typedef vector<T> argument_type;
|
||
typedef std::size_t result_type;
|
||
result_type operator()(argument_type const &in) const
|
||
{
|
||
size_t size = in.size();
|
||
size_t seed = 0;
|
||
for (size_t i = 0; i < size; i++)
|
||
// Combine the hash of the current vector with the hashes of the
|
||
// previous ones
|
||
hash_combine(seed, in[i]);
|
||
return seed;
|
||
}
|
||
};
|
||
}
|
||
|
||
namespace spot
|
||
{
|
||
inline void
|
||
assign_color(acc_cond::mark_t &mark, unsigned col)
|
||
{
|
||
if (col < SPOT_MAX_ACCSETS)
|
||
mark.set(col);
|
||
else
|
||
acc_cond::mark_t{SPOT_MAX_ACCSETS};
|
||
}
|
||
|
||
// Describes if we want to test if it is a Büchi, co-Büchi,… type automaton.
|
||
enum cond_kind
|
||
{
|
||
BUCHI,
|
||
CO_BUCHI,
|
||
// A parity condition with a Inf as outermost term
|
||
INF_PARITY,
|
||
// A parity condition with a Fin as outermost term
|
||
FIN_PARITY
|
||
};
|
||
|
||
// This enum describes the status of an edge
|
||
enum edge_status
|
||
{
|
||
NOT_MARKED,
|
||
MARKED,
|
||
IMPOSSIBLE,
|
||
LINK_SCC
|
||
};
|
||
|
||
static bool
|
||
cond_type_main_aux(const twa_graph_ptr &aut, const cond_kind kind,
|
||
const bool need_equivalent,
|
||
std::vector<edge_status> &status,
|
||
std::vector<acc_cond::mark_t> &res_colors,
|
||
acc_cond &new_cond, bool &was_able_to_color,
|
||
unsigned max_col)
|
||
{
|
||
auto& ev = aut->edge_vector();
|
||
const auto ev_size = ev.size();
|
||
const auto aut_init = aut->get_init_state_number();
|
||
was_able_to_color = false;
|
||
status = std::vector<edge_status>(ev_size, NOT_MARKED);
|
||
res_colors = std::vector<acc_cond::mark_t>(ev_size);
|
||
// Used by accepting_transitions_scc.
|
||
auto keep = std::unique_ptr<bitvect>(make_bitvect(ev_size));
|
||
keep->set_all();
|
||
|
||
// Number of edges colored by the procedure, used to test equivalence for
|
||
// parity
|
||
unsigned nb_colored = 0;
|
||
|
||
// We need to say that a transition between 2 SCC doesn't have to get a
|
||
// color.
|
||
scc_info si(aut, aut_init, nullptr, nullptr, scc_info_options::NONE);
|
||
status[0] = LINK_SCC;
|
||
if (si.scc_count() > 1)
|
||
{
|
||
for (unsigned edge_number = 1; edge_number < ev_size; ++edge_number)
|
||
{
|
||
auto& e = ev[edge_number];
|
||
if (si.scc_of(e.src) != si.scc_of(e.dst))
|
||
{
|
||
status[edge_number] = LINK_SCC;
|
||
++nb_colored;
|
||
}
|
||
}
|
||
}
|
||
|
||
// If we need to convert to (co-)Büchi, we have to search one accepting
|
||
// set. With parity there is no limit.
|
||
bool want_parity = kind == cond_kind::FIN_PARITY ||
|
||
kind == cond_kind::INF_PARITY;
|
||
unsigned max_iter = want_parity ? -1U : 1;
|
||
|
||
unsigned color = max_col;
|
||
// Do we want always accepting transitions?
|
||
// Don't consider CO_BUCHI as it is done by Büchi
|
||
bool search_inf = kind != cond_kind::FIN_PARITY;
|
||
|
||
using filter_data_t = std::pair<const_twa_graph_ptr,
|
||
std::vector<edge_status> &>;
|
||
|
||
scc_info::edge_filter filter =
|
||
[](const twa_graph::edge_storage_t &t, unsigned, void *data)
|
||
-> scc_info::edge_filter_choice
|
||
{
|
||
auto &d = *static_cast<filter_data_t *>(data);
|
||
// We only keep transitions that can be marked
|
||
if (d.second[d.first->edge_number(t)] == NOT_MARKED)
|
||
return scc_info::edge_filter_choice::keep;
|
||
else
|
||
return scc_info::edge_filter_choice::cut;
|
||
};
|
||
std::vector<bool> not_decidable_transitions(ev_size, false);
|
||
auto aut_acc = aut->get_acceptance();
|
||
auto aut_acc_comp = aut_acc.complement();
|
||
for (unsigned iter = 0; iter < max_iter; ++iter)
|
||
{
|
||
// Share the code with Büchi-type
|
||
if (kind == CO_BUCHI)
|
||
std::swap(aut_acc, aut_acc_comp);
|
||
std::fill(not_decidable_transitions.begin(),
|
||
not_decidable_transitions.end(), false);
|
||
auto cond = acc_cond(search_inf ? aut_acc_comp : aut_acc);
|
||
auto filter_data = filter_data_t{aut, status};
|
||
scc_info si(aut, aut_init, filter, &filter_data,
|
||
scc_info_options::TRACK_STATES);
|
||
if (search_inf)
|
||
si.determine_unknown_acceptance();
|
||
bool worked = false;
|
||
unsigned ssc_size = si.scc_count();
|
||
for (unsigned scc = 0; scc < ssc_size; ++scc)
|
||
{
|
||
// scc_info can detect that we will not be able to find an
|
||
// accepting cycle.
|
||
if ((search_inf && si.is_accepting_scc(scc)) || !search_inf)
|
||
{
|
||
accepting_transitions_scc(si, scc, cond, {},
|
||
not_decidable_transitions, *keep);
|
||
for (auto &e : si.inner_edges_of(scc))
|
||
{
|
||
auto edge_number = aut->edge_number(e);
|
||
if (!not_decidable_transitions[edge_number])
|
||
{
|
||
assert(!res_colors[edge_number]);
|
||
if (color != -1U)
|
||
assign_color(res_colors[edge_number], color);
|
||
was_able_to_color = true;
|
||
status[edge_number] = MARKED;
|
||
++nb_colored;
|
||
keep->clear(edge_number);
|
||
worked = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (color-- == -1U)
|
||
break;
|
||
search_inf = !search_inf;
|
||
// If we were not able to add color, we have to add status 2 to
|
||
// remaining transitions.
|
||
if (!worked && !need_equivalent)
|
||
{
|
||
std::replace(status.begin(), status.end(), NOT_MARKED, IMPOSSIBLE);
|
||
break;
|
||
}
|
||
}
|
||
|
||
acc_cond::acc_code new_code;
|
||
switch (kind)
|
||
{
|
||
case cond_kind::BUCHI:
|
||
new_code = acc_cond::acc_code::buchi();
|
||
break;
|
||
case cond_kind::CO_BUCHI:
|
||
new_code = acc_cond::acc_code::cobuchi();
|
||
break;
|
||
case cond_kind::INF_PARITY:
|
||
case cond_kind::FIN_PARITY:
|
||
new_code = acc_cond::acc_code::parity_max(
|
||
kind == cond_kind::INF_PARITY, SPOT_MAX_ACCSETS);
|
||
break;
|
||
}
|
||
|
||
new_cond = acc_cond(new_code);
|
||
|
||
// We check parity
|
||
if (need_equivalent)
|
||
{
|
||
// For parity, it's equivalent if every transition has a color
|
||
// (status 1) or links 2 SCCs.
|
||
if (kind == cond_kind::INF_PARITY || kind == cond_kind::FIN_PARITY)
|
||
return nb_colored == ev_size - 1;
|
||
else
|
||
{
|
||
// For Büchi, we remove the transitions that have {0} in the
|
||
// result from aut and if there is an accepting cycle, res is not
|
||
// equivalent to aut.
|
||
// For co-Büchi, it's the same but we don't want to find a
|
||
// rejecting cycle.
|
||
using filter_data_t = std::pair<const_twa_graph_ptr, bitvect &>;
|
||
|
||
scc_info::edge_filter filter =
|
||
[](const twa_graph::edge_storage_t &t, unsigned, void *data)
|
||
-> scc_info::edge_filter_choice
|
||
{
|
||
auto &d = *static_cast<filter_data_t *>(data);
|
||
if (d.second.get(d.first->edge_number(t)))
|
||
return scc_info::edge_filter_choice::keep;
|
||
else
|
||
return scc_info::edge_filter_choice::cut;
|
||
};
|
||
|
||
if (kind == CO_BUCHI)
|
||
aut->set_acceptance(acc_cond(aut_acc));
|
||
|
||
filter_data_t filter_data = {aut, *keep};
|
||
scc_info si(aut, aut_init, filter, &filter_data);
|
||
si.determine_unknown_acceptance();
|
||
const auto num_scc = si.scc_count();
|
||
for (unsigned scc = 0; scc < num_scc; ++scc)
|
||
if (si.is_accepting_scc(scc))
|
||
{
|
||
if (kind == CO_BUCHI)
|
||
aut->set_acceptance(acc_cond(aut_acc_comp));
|
||
return false;
|
||
}
|
||
if (kind == CO_BUCHI)
|
||
aut->set_acceptance(acc_cond(aut_acc_comp));
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static twa_graph_ptr
|
||
cond_type_main(const twa_graph_ptr &aut, const cond_kind kind,
|
||
bool &was_able_to_color, unsigned max_color)
|
||
{
|
||
std::vector<acc_cond::mark_t> res_colors;
|
||
std::vector<edge_status> status;
|
||
acc_cond new_cond;
|
||
if (cond_type_main_aux(aut, kind, true, status, res_colors, new_cond,
|
||
was_able_to_color, max_color))
|
||
{
|
||
auto res = make_twa_graph(aut, twa::prop_set::all());
|
||
// GCC 7 warns about potential null pointer dereference.
|
||
SPOT_ASSUME(res);
|
||
auto &res_vector = res->edge_vector();
|
||
unsigned rv_size = res_vector.size();
|
||
for (unsigned i = 1; i < rv_size; ++i)
|
||
res_vector[i].acc = res_colors[i];
|
||
res->set_acceptance(new_cond);
|
||
return res;
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
twa_graph_ptr
|
||
parity_type_to_parity(const twa_graph_ptr &aut)
|
||
{
|
||
bool odd_cond, max_cond;
|
||
bool parit = aut->acc().is_parity(max_cond, odd_cond);
|
||
// If it is parity, we just copy
|
||
if (parit)
|
||
{
|
||
if (!max_cond)
|
||
return change_parity(aut, parity_kind_max, parity_style_any);
|
||
auto res = make_twa_graph(aut, twa::prop_set::all());
|
||
res->copy_acceptance_of(aut);
|
||
return res;
|
||
}
|
||
bool was_able_to_color;
|
||
// If the automaton is parity-type with a condition that has Inf as
|
||
// outermost term
|
||
auto res = cond_type_main(aut, cond_kind::INF_PARITY,
|
||
was_able_to_color, SPOT_MAX_ACCSETS - 1);
|
||
|
||
// If it was impossible to find an accepting edge, it is perhaps possible
|
||
// to find a rejecting transition
|
||
if (res == nullptr && !was_able_to_color)
|
||
res = cond_type_main(aut, cond_kind::FIN_PARITY,
|
||
was_able_to_color, SPOT_MAX_ACCSETS - 1);
|
||
if (res)
|
||
{
|
||
res->prop_state_acc(false);
|
||
reduce_parity_here(res);
|
||
}
|
||
return res;
|
||
}
|
||
|
||
twa_graph_ptr
|
||
buchi_type_to_buchi(const twa_graph_ptr &aut)
|
||
{
|
||
bool useless;
|
||
return cond_type_main(aut, cond_kind::BUCHI, useless, 0);
|
||
}
|
||
|
||
twa_graph_ptr
|
||
co_buchi_type_to_co_buchi(const twa_graph_ptr &aut)
|
||
{
|
||
bool useless;
|
||
return cond_type_main(aut, cond_kind::CO_BUCHI, useless, 0);
|
||
}
|
||
|
||
// New version for paritizing
|
||
|
||
// data type used in a memory for CAR and IAR.
|
||
// TAR is a particular case
|
||
#if MAX_ACCSETS < UCHAR_MAX
|
||
using memory_type = unsigned char;
|
||
#define MAX_MEM_ELEM UCHAR_MAX
|
||
#elif MAX_ACCSETS < USHRT_MAX
|
||
using memory_type = unsigned short;
|
||
#define MAX_MEM_ELEM USHRT_MAX
|
||
#else
|
||
using memory_type = unsigned;
|
||
#define MAX_MEM_ELEM UINT_MAX
|
||
#endif
|
||
|
||
template<typename T>
|
||
using memory = std::vector<T>;
|
||
|
||
// Maps a state of the automaton to a parity_state
|
||
class state_2_lar
|
||
{
|
||
public:
|
||
// If to_parity wants to find the newest or the oldest or both, we
|
||
// adapt the algorithms
|
||
enum memory_order
|
||
{
|
||
ONLY_NEWEST,
|
||
ONLY_OLDEST,
|
||
BOTH
|
||
};
|
||
|
||
class node
|
||
{
|
||
public:
|
||
// Color that lead to this node
|
||
memory_type color_;
|
||
// For a state in states_, any child can be taken. While a unique state
|
||
// could be used when we search an existing state, here we have
|
||
// to consider opt_.search_ex = False, opt_.use_last_post_process = True.
|
||
// This configuration can lead to 2 states in the same node. For example
|
||
// if we add [0 1 | 2 3] and [0 1 | 3 2] where '|' says which part of the
|
||
// memory can be reordered (right).
|
||
std::vector<unsigned> states_;
|
||
std::vector<node *> children_;
|
||
// A timer used to detect which child is the oldest
|
||
unsigned timer_;
|
||
|
||
node() : node(MAX_MEM_ELEM, -1U)
|
||
{
|
||
}
|
||
|
||
node(memory_type val, unsigned timer) : color_(val), timer_(timer)
|
||
{
|
||
}
|
||
|
||
~node()
|
||
{
|
||
for (auto c : children_)
|
||
delete c;
|
||
}
|
||
};
|
||
|
||
std::vector<node *> nodes_;
|
||
memory_order order_;
|
||
unsigned timer_;
|
||
|
||
state_2_lar() : timer_(0)
|
||
{
|
||
}
|
||
|
||
void
|
||
init(unsigned nb_states, memory_order order)
|
||
{
|
||
order_ = order;
|
||
nodes_.reserve(nb_states);
|
||
for (unsigned i = 0; i < nb_states; ++i)
|
||
nodes_.push_back(new node());
|
||
}
|
||
|
||
~state_2_lar()
|
||
{
|
||
for (auto x : nodes_)
|
||
delete x;
|
||
}
|
||
|
||
void
|
||
add_new_path(unsigned state, const memory<memory_type> &vals,
|
||
unsigned res_state, unsigned nb_seen)
|
||
{
|
||
++timer_;
|
||
node *current = nodes_[state];
|
||
// Position in vals
|
||
int pos = vals.size() - 1;
|
||
while (true)
|
||
{
|
||
if (pos == (int)(nb_seen - 1))
|
||
current->states_.push_back(res_state);
|
||
if (pos == -1)
|
||
break;
|
||
const unsigned current_val = vals[pos];
|
||
auto child = std::find_if(current->children_.begin(),
|
||
current->children_.end(),
|
||
[&](const auto &child) constexpr
|
||
{ return child->color_ == current_val; });
|
||
// If we don't have a child with the corresponding color…
|
||
if (child == current->children_.end())
|
||
{
|
||
auto nn = new node(current_val, timer_);
|
||
current->children_.push_back(nn);
|
||
current = nn;
|
||
}
|
||
else
|
||
{
|
||
// If get_compatible_state wants the most recent
|
||
// (opt_.use_last or opt_.use_last_post_process), we help this
|
||
// function by moving this node to the last position.
|
||
// Otherwise the oldest leaf will be reachable from the first child.
|
||
// If we have use_last = false and use_last_post_process = true,
|
||
// we need to access to the oldest and newest child. As the tree is
|
||
// smallest when we want to access to the oldest value, we continue
|
||
// to move the value to the last position and compute the oldest
|
||
// child in get_compatible_state.
|
||
if (order_ != memory_order::ONLY_OLDEST)
|
||
{
|
||
std::iter_swap(child, current->children_.end() - 1);
|
||
current = current->children_.back();
|
||
}
|
||
else
|
||
current = *child;
|
||
}
|
||
--pos;
|
||
}
|
||
}
|
||
|
||
unsigned
|
||
get_compatible_state(unsigned state, const memory<memory_type> &m,
|
||
unsigned seen_nb,
|
||
bool use_last) const
|
||
{
|
||
int pos = m.size() - 1;
|
||
unsigned res = -1U;
|
||
node *current = nodes_[state];
|
||
while (true)
|
||
{
|
||
const auto ¤t_states = current->states_;
|
||
if (!current_states.empty())
|
||
res = use_last ? current_states.back() : current_states.front();
|
||
|
||
const auto ¤t_children = current->children_;
|
||
if (current_children.empty())
|
||
{
|
||
assert(current->color_ == MAX_MEM_ELEM || pos == -1);
|
||
return res;
|
||
}
|
||
// If we are in the part of the memory where the order does not matter,
|
||
// we just take the oldest/newest state.
|
||
if (pos < (int)seen_nb)
|
||
{
|
||
if (order_ == BOTH)
|
||
{
|
||
if (!use_last)
|
||
current = *std::min_element(
|
||
current_children.begin(), current_children.end(),
|
||
[](const auto &x, const auto &y) constexpr
|
||
{ return x->timer_ < y->timer_; });
|
||
else
|
||
current = current_children.back();
|
||
}
|
||
else
|
||
{
|
||
// add_new_path constructed the tree such that the oldest/newest
|
||
// leaf is reachable from the first child.
|
||
current = use_last ? current_children.back()
|
||
: current_children.front();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
auto current_val = m[pos];
|
||
auto ch = std::find_if(
|
||
current_children.begin(), current_children.end(),
|
||
[&](const auto &x) constexpr
|
||
{ return x->color_ == current_val; });
|
||
if (ch != current_children.end())
|
||
current = *ch;
|
||
else
|
||
return -1U;
|
||
}
|
||
--pos;
|
||
}
|
||
}
|
||
};
|
||
|
||
class to_parity_generator
|
||
{
|
||
private:
|
||
class relation
|
||
{
|
||
public:
|
||
// Size of the matrix
|
||
unsigned size_;
|
||
// A line/column is indexed by a partial memory
|
||
const std::vector<memory<memory_type>> labels_;
|
||
// Matrix such that vals_[x][y] = ⊤ ⇔ vals_[x] > vals_[y]
|
||
std::vector<bool> vals_;
|
||
|
||
inline bool
|
||
at(unsigned i, unsigned j) const
|
||
{
|
||
return vals_.at(i * size_ + j);
|
||
}
|
||
|
||
inline void
|
||
set(unsigned i, unsigned j, bool val)
|
||
{
|
||
vals_[i * size_ + j] = val;
|
||
}
|
||
|
||
// Test if m1 ⊆ m2
|
||
bool is_included(memory<memory_type> m1, memory<memory_type> m2)
|
||
{
|
||
if (m1.size() > m2.size())
|
||
return false;
|
||
assert(std::is_sorted(m1.begin(), m1.end()));
|
||
assert(std::is_sorted(m2.begin(), m2.end()));
|
||
memory<memory_type> diff;
|
||
std::set_union(m1.begin(), m1.end(), m2.begin(), m2.end(),
|
||
std::inserter(diff, diff.begin()));
|
||
return diff.size() == m2.size();
|
||
}
|
||
|
||
// Supposes that there is no duplicates.
|
||
relation(std::vector<memory<memory_type>> &labels)
|
||
: size_(labels.size()), labels_(labels)
|
||
{
|
||
unsigned long long size_vals;
|
||
if (__builtin_umulll_overflow(size_, size_, &size_vals))
|
||
throw std::bad_alloc();
|
||
vals_ = std::vector<bool>(size_vals);
|
||
for (unsigned i = 0; i < size_; ++i)
|
||
for (unsigned j = 0; j < size_; ++j)
|
||
// We cannot have vals_[i] > vals_[j] and vals_[j] > vals_[i]
|
||
if (!at(j, i))
|
||
set(i, j, (i != j && is_included(labels_[j], labels_[i])));
|
||
// Remove x > z if ∃y s.t. x > y > z
|
||
simplify_relation();
|
||
}
|
||
|
||
// Apply a transitive reduction
|
||
void
|
||
simplify_relation()
|
||
{
|
||
for (unsigned j = 0; j < size_; ++j)
|
||
for (unsigned i = 0; i < size_; ++i)
|
||
if (at(i, j))
|
||
for (unsigned k = 0; k < size_; ++k)
|
||
if (at(j, k))
|
||
set(i, k, false);
|
||
}
|
||
|
||
template<typename T>
|
||
void
|
||
add_to_res_(const memory<T> ¤t,
|
||
const memory<T> &other,
|
||
memory<T> &result)
|
||
{
|
||
assert(std::is_sorted(current.begin(), current.end()));
|
||
assert(std::is_sorted(other.begin(), other.end()));
|
||
std::set_difference(current.begin(), current.end(),
|
||
other.begin(), other.end(),
|
||
std::inserter(result, result.end()));
|
||
}
|
||
|
||
// Gives a compatible ordered partial memory for the partial memory
|
||
// partial_mem.
|
||
memory<memory_type>
|
||
find_order(const memory<memory_type> &partial_mem)
|
||
{
|
||
// Now if we want to find an order, we start from the line
|
||
// that contains partial_mem in the matrix, we find a more restrictive
|
||
// order and add the value that are used in partial_mem but not in this
|
||
// "child" value.
|
||
// The call to simplify_relation implies that we are sure we have
|
||
// used the longest possible path.
|
||
memory<memory_type> result;
|
||
auto elem = std::find(labels_.begin(), labels_.end(), partial_mem);
|
||
assert(elem != labels_.end());
|
||
// Line that contains partial_mem
|
||
unsigned i = std::distance(labels_.begin(), elem);
|
||
while (true)
|
||
{
|
||
// The interval corresponding to the line i
|
||
auto vals_i_begin = vals_.begin() + (i * size_);
|
||
auto vals_i_end = vals_i_begin + size_;
|
||
// End of line i
|
||
auto child = std::find(vals_i_begin, vals_i_end, true);
|
||
// If there is a more restrictive memory, we use this "child"
|
||
if (child != vals_i_end)
|
||
{
|
||
unsigned child_pos = std::distance(vals_i_begin, child);
|
||
add_to_res_(labels_[i], labels_[child_pos], result);
|
||
i = child_pos;
|
||
}
|
||
// If there is no more restrictive memory, we just add the remaining
|
||
// memory.
|
||
else
|
||
{
|
||
add_to_res_(labels_[i], {}, result);
|
||
break;
|
||
}
|
||
}
|
||
// The order want that a value that is in the lowest value is a
|
||
// the head.
|
||
std::reverse(result.begin(), result.end());
|
||
return result;
|
||
}
|
||
};
|
||
|
||
class scc_info_to_parity
|
||
{
|
||
private:
|
||
scc_info si_;
|
||
|
||
public:
|
||
scc_info_to_parity(const const_twa_graph_ptr aut,
|
||
const acc_cond::mark_t removed = {})
|
||
: si_(scc_and_mark_filter(aut, removed))
|
||
{
|
||
}
|
||
|
||
scc_info_to_parity(const scc_info lower_si,
|
||
const std::shared_ptr<bitvect> keep)
|
||
: si_(scc_and_mark_filter(lower_si, 0, acc_cond::mark_t{}, *keep),
|
||
scc_info_options::NONE)
|
||
{
|
||
}
|
||
|
||
std::vector<twa_graph_ptr>
|
||
split_aut(acc_cond::mark_t mark = {})
|
||
{
|
||
auto aut = si_.get_aut();
|
||
const auto num_scc = si_.scc_count();
|
||
const unsigned aut_num_states = aut->num_states();
|
||
std::vector<twa_graph_ptr> res(num_scc);
|
||
std::vector<unsigned> aut_to_res;
|
||
aut_to_res.reserve(aut_num_states);
|
||
for (auto &g : res)
|
||
{
|
||
g = make_twa_graph(aut->get_dict());
|
||
g->copy_ap_of(aut);
|
||
g->copy_acceptance_of(aut);
|
||
g->prop_copy(aut, {true, true, false, false, false, true});
|
||
auto orig = new std::vector<unsigned>();
|
||
g->set_named_prop("original-states", orig);
|
||
}
|
||
const auto tp_orig_aut =
|
||
aut->get_named_prop<std::vector<unsigned>>("original-states");
|
||
for (unsigned i = 0; i < aut_num_states; ++i)
|
||
{
|
||
unsigned scc_i = si_.scc_of(i);
|
||
auto &g = res[scc_i];
|
||
unsigned ns = g->new_state();
|
||
unsigned ori = tp_orig_aut ? (*tp_orig_aut)[i] : i;
|
||
auto pr = g->get_named_prop<std::vector<unsigned>>("original-states");
|
||
pr->push_back(ori);
|
||
aut_to_res.push_back(ns);
|
||
}
|
||
|
||
for (auto &e : aut->edges())
|
||
{
|
||
unsigned src_scc = si_.scc_of(e.src);
|
||
unsigned dst_scc = si_.scc_of(e.dst);
|
||
if (src_scc == dst_scc && !(e.acc & mark))
|
||
res[src_scc]->new_edge(aut_to_res[e.src], aut_to_res[e.dst],
|
||
e.cond, e.acc);
|
||
}
|
||
return res;
|
||
}
|
||
|
||
std::vector<twa_graph_ptr>
|
||
split_aut(const std::shared_ptr<bitvect> &keep)
|
||
{
|
||
auto aut = si_.get_aut();
|
||
const auto num_scc = si_.scc_count();
|
||
const unsigned aut_num_states = aut->num_states();
|
||
std::vector<twa_graph_ptr> res(num_scc);
|
||
std::vector<unsigned> aut_to_res;
|
||
aut_to_res.reserve(aut_num_states);
|
||
for (auto &g : res)
|
||
{
|
||
g = make_twa_graph(aut->get_dict());
|
||
g->copy_ap_of(aut);
|
||
g->copy_acceptance_of(aut);
|
||
g->prop_copy(aut, {true, true, false, false, false, true});
|
||
auto orig = new std::vector<unsigned>();
|
||
g->set_named_prop("original-states", orig);
|
||
}
|
||
const auto tp_orig_aut =
|
||
aut->get_named_prop<std::vector<unsigned>>("original-states");
|
||
for (unsigned i = 0; i < aut_num_states; ++i)
|
||
{
|
||
unsigned scc_i = si_.scc_of(i);
|
||
auto &g = res[scc_i];
|
||
unsigned ns = g->new_state();
|
||
unsigned ori = tp_orig_aut ? (*tp_orig_aut)[i] : i;
|
||
auto pr = g->get_named_prop<std::vector<unsigned>>("original-states");
|
||
pr->push_back(ori);
|
||
aut_to_res.push_back(ns);
|
||
}
|
||
|
||
const auto &ev = si_.get_aut()->edge_vector();
|
||
auto ev_size = ev.size();
|
||
for (unsigned i = 0; i < ev_size; ++i)
|
||
if (keep->get(i))
|
||
{
|
||
auto &e = ev[i];
|
||
unsigned scc_src = si_.scc_of(e.src);
|
||
if (scc_src == si_.scc_of(e.dst))
|
||
res[scc_src]->new_edge(aut_to_res[e.src], aut_to_res[e.dst],
|
||
e.cond, e.acc);
|
||
}
|
||
return res;
|
||
}
|
||
|
||
unsigned scc_count()
|
||
{
|
||
return si_.scc_count();
|
||
}
|
||
|
||
unsigned scc_of(unsigned state)
|
||
{
|
||
return si_.scc_of(state);
|
||
}
|
||
};
|
||
|
||
// Original automaton
|
||
const const_twa_graph_ptr aut_;
|
||
// Resulting parity automaton
|
||
twa_graph_ptr res_;
|
||
// options
|
||
to_parity_options opt_;
|
||
// nullptr if opt_.pretty_print is false
|
||
std::vector<std::string> *names_ = nullptr;
|
||
// original_states. Is propagated if the original automaton has
|
||
// this named property
|
||
std::vector<unsigned> *orig_ = nullptr;
|
||
scc_info_to_parity si_;
|
||
bool need_purge_ = false;
|
||
// Tells if we are constructing a parity max odd
|
||
bool is_odd_ = false;
|
||
// min_color used in the automaton + 1 (result of max_set).
|
||
unsigned min_color_used_ = -1U;
|
||
unsigned max_color_scc_ = 0;
|
||
unsigned max_color_used_ = 0;
|
||
std::vector<unsigned> state_to_res_;
|
||
std::vector<unsigned> res_to_aut_;
|
||
// Map a state of aut_ to every copy of this state. Used by a recursive call
|
||
// to to_parity by parity_prefix for example
|
||
std::vector<std::vector<unsigned>> *state_to_nums_ = nullptr;
|
||
unsigned algo_used_ = 0;
|
||
|
||
enum algorithm
|
||
{
|
||
CAR = 1,
|
||
IAR_RABIN = 1 << 1,
|
||
IAR_STREETT = 1 << 2,
|
||
TAR = 1 << 3,
|
||
RABIN_TO_BUCHI = 1 << 4,
|
||
STREETT_TO_COBUCHI = 1 << 5,
|
||
PARITY_TYPE = 1 << 6,
|
||
BUCHI_TYPE = 1 << 7,
|
||
CO_BUCHI_TYPE = 1 << 8,
|
||
PARITY_EQUIV = 1 << 9,
|
||
PARITY_PREFIX = 1 << 10,
|
||
PARITY_PREFIX_GENERAL = 1 << 11,
|
||
GENERIC_EMPTINESS = 1 << 12,
|
||
PARTIAL_DEGEN = 1 << 13,
|
||
ACC_CLEAN = 1 << 14,
|
||
NONE = 1 << 15
|
||
};
|
||
|
||
static std::string
|
||
algorithm_to_str(const algorithm &algo)
|
||
{
|
||
switch (algo)
|
||
{
|
||
case CAR:
|
||
return "CAR";
|
||
case IAR_RABIN:
|
||
return "IAR (Rabin)";
|
||
case IAR_STREETT:
|
||
return "IAR (Streett)";
|
||
case TAR:
|
||
return "TAR";
|
||
case NONE:
|
||
return "None";
|
||
case BUCHI_TYPE:
|
||
return "Büchi-type";
|
||
case CO_BUCHI_TYPE:
|
||
return "co-Büchi-type";
|
||
case PARITY_TYPE:
|
||
return "Parity-type";
|
||
case PARITY_EQUIV:
|
||
return "Parity equivalent";
|
||
case GENERIC_EMPTINESS:
|
||
return "Generic emptiness";
|
||
case STREETT_TO_COBUCHI:
|
||
return "Streett to co-Büchi";
|
||
case RABIN_TO_BUCHI:
|
||
return "Rabin to Büchi";
|
||
case PARITY_PREFIX:
|
||
return "Parity-prefix";
|
||
case PARITY_PREFIX_GENERAL:
|
||
return "Parity-prefix general";
|
||
case PARTIAL_DEGEN:
|
||
return "Partial degeneralization";
|
||
case ACC_CLEAN:
|
||
return "acceptance cleanup";
|
||
}
|
||
SPOT_UNREACHABLE();
|
||
}
|
||
|
||
template<typename T>
|
||
struct to_parity_state
|
||
{
|
||
unsigned state;
|
||
unsigned state_scc;
|
||
memory<T> mem;
|
||
|
||
to_parity_state(unsigned st, unsigned st_scc, memory<T> m) :
|
||
state(st), state_scc(st_scc), mem(m)
|
||
{}
|
||
|
||
to_parity_state(const to_parity_state &) = default;
|
||
to_parity_state(to_parity_state &&) noexcept = default;
|
||
|
||
~to_parity_state() noexcept = default;
|
||
|
||
bool
|
||
operator<(const to_parity_state &other) const
|
||
{
|
||
if (state < other.state)
|
||
return true;
|
||
if (state > other.state)
|
||
return false;
|
||
if (state_scc < other.state_scc)
|
||
return true;
|
||
if (state_scc > other.state_scc)
|
||
return false;
|
||
if (mem < other.mem)
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
std::string
|
||
to_str(const algorithm &algo) const
|
||
{
|
||
std::stringstream s;
|
||
s << state;
|
||
// An empty memory does not mean that we don't use LAR. For example
|
||
// if the condition is true. We don't display a useless memory.
|
||
if (!mem.empty())
|
||
{
|
||
s << ",[";
|
||
const char delim = ',';
|
||
s << ((unsigned)mem[0]);
|
||
auto mem_size = mem.size();
|
||
for (unsigned i = 1; i < mem_size; ++i)
|
||
s << delim << ((unsigned)mem[i]);
|
||
s << ']';
|
||
}
|
||
s << ',' << algorithm_to_str(algo);
|
||
return s.str();
|
||
}
|
||
|
||
bool operator==(const to_parity_state &other) const
|
||
{
|
||
return state == other.state && state_scc == other.state_scc
|
||
&& mem == other.mem;
|
||
}
|
||
};
|
||
|
||
template<typename T>
|
||
struct to_parity_hash
|
||
{
|
||
size_t operator()(to_parity_state<T> const &tp) const
|
||
{
|
||
size_t result = std::hash<memory<T>>{}(tp.mem);
|
||
std::hash_combine<unsigned>(result, tp.state);
|
||
std::hash_combine<unsigned>(result, tp.state_scc);
|
||
return result;
|
||
}
|
||
};
|
||
|
||
template<typename T>
|
||
unsigned
|
||
add_res_state(const algorithm &algo, const to_parity_state<T> &ps)
|
||
{
|
||
if (names_)
|
||
names_->emplace_back(ps.to_str(algo));
|
||
orig_->push_back(ps.state);
|
||
auto res = res_->new_state();
|
||
if (opt_.datas)
|
||
{
|
||
algo_used_ |= algo;
|
||
++opt_.datas->nb_states_created;
|
||
}
|
||
assert(ps.state < aut_->num_states());
|
||
// state_to_res_ could be updated even if there is already a value.
|
||
// However it would lead to a result close to BSCC.
|
||
// So it is easier to show the influence of BSCC when the value is not
|
||
// changed when there is already a value.
|
||
if (state_to_res_[ps.state] == -1U)
|
||
state_to_res_[ps.state] = res;
|
||
if (state_to_nums_)
|
||
{
|
||
assert(ps.state < state_to_nums_->size());
|
||
(*state_to_nums_)[ps.state].push_back(res);
|
||
}
|
||
res_to_aut_.push_back(ps.state);
|
||
return res;
|
||
}
|
||
|
||
unsigned
|
||
add_res_edge(unsigned res_src, unsigned res_dst,
|
||
const acc_cond::mark_t &mark, const bdd &cond,
|
||
const bool can_merge_edge = true,
|
||
robin_hood::unordered_map<long long, unsigned>*
|
||
edge_cache = nullptr)
|
||
{
|
||
// In a parity automaton we just need the maximal value
|
||
unsigned simax = mark.max_set();
|
||
|
||
const bool need_cache = edge_cache != nullptr && can_merge_edge;
|
||
long long key = 0;
|
||
if (need_cache)
|
||
{
|
||
constexpr auto unsignedsize = sizeof(unsigned) * 8;
|
||
key = (long long)simax << unsignedsize | res_dst;
|
||
auto cache_value = edge_cache->find(key);
|
||
if (cache_value != edge_cache->end())
|
||
{
|
||
auto edge_index = cache_value->second;
|
||
auto &existing_edge = res_->edge_vector()[edge_index];
|
||
existing_edge.cond |= cond;
|
||
return edge_index;
|
||
}
|
||
}
|
||
|
||
auto simplified = mark ? acc_cond::mark_t{simax - 1}
|
||
: acc_cond::mark_t{};
|
||
assert(res_src != -1U);
|
||
assert(res_dst != -1U);
|
||
|
||
max_color_scc_ = std::max(max_color_scc_, simax);
|
||
min_color_used_ = std::min(min_color_used_, simax);
|
||
max_color_used_ = std::max(max_color_used_, simax);
|
||
|
||
auto new_edge_num = res_->new_edge(res_src, res_dst, cond, simplified);
|
||
if (need_cache)
|
||
edge_cache->emplace(std::make_pair(key, new_edge_num));
|
||
|
||
if (opt_.datas)
|
||
++opt_.datas->nb_edges_created;
|
||
return new_edge_num;
|
||
}
|
||
|
||
// copy
|
||
using coloring_function =
|
||
std::function<acc_cond::mark_t(const twa_graph::edge_storage_t &)>;
|
||
|
||
void
|
||
apply_copy_general(const const_twa_graph_ptr &sub_automaton,
|
||
const coloring_function &col_fun,
|
||
const algorithm &algo)
|
||
{
|
||
if (opt_.datas)
|
||
algo_used_ |= algo;
|
||
auto init_states =
|
||
sub_automaton->get_named_prop<std::vector<unsigned>>("original-states");
|
||
assert(init_states);
|
||
std::vector<unsigned> state_2_res_local;
|
||
auto sub_aut_ns = sub_automaton->num_states();
|
||
state_2_res_local.reserve(sub_aut_ns);
|
||
for (unsigned state = 0; state < sub_aut_ns; ++state)
|
||
{
|
||
to_parity_state<memory_type> ps = {(*init_states)[state], state, {}};
|
||
state_2_res_local.push_back(add_res_state(algo, ps));
|
||
}
|
||
for (auto &e : sub_automaton->edges())
|
||
{
|
||
auto new_mark = col_fun(e);
|
||
add_res_edge(state_2_res_local[e.src], state_2_res_local[e.dst],
|
||
new_mark, e.cond);
|
||
}
|
||
}
|
||
|
||
// Case where one color is replaced by another.
|
||
// new_colors is a vector such that new_colors[i + 1] = j means that the
|
||
// color i is replaced by j. new_colors[0] is the value for an uncolored
|
||
// edge.
|
||
void
|
||
apply_copy(const const_twa_graph_ptr &sub_aut,
|
||
const std::vector<unsigned> &new_colors,
|
||
const algorithm &algo)
|
||
{
|
||
auto col_fun = [&](const twa_graph::edge_storage_t &edge)
|
||
{
|
||
acc_cond::mark_t res{};
|
||
for (auto c : edge.acc.sets())
|
||
{
|
||
auto new_col = new_colors[c + 1];
|
||
if (new_col != -1U)
|
||
assign_color(res, new_col);
|
||
}
|
||
if (!edge.acc && new_colors[0] != -1U)
|
||
assign_color(res, new_colors[0]);
|
||
return res;
|
||
};
|
||
apply_copy_general(sub_aut, col_fun, algo);
|
||
}
|
||
|
||
// Case where new_color is a function such that edge_vector[i] should
|
||
// be colored by new_color[i].
|
||
void
|
||
apply_copy_edge_index(const const_twa_graph_ptr &sub_aut,
|
||
const std::vector<unsigned> &new_color,
|
||
const algorithm &algo)
|
||
{
|
||
auto col_fun = [&](const twa_graph::edge_storage_t &edge)
|
||
{
|
||
auto res = new_color[sub_aut->edge_number(edge)];
|
||
if (res == -1U)
|
||
return acc_cond::mark_t{};
|
||
return acc_cond::mark_t{res};
|
||
};
|
||
apply_copy_general(sub_aut, col_fun, algo);
|
||
}
|
||
|
||
// Create a memory for the first state created by apply_lar.
|
||
// If the algorithm is IAR, it also fills pairs_indices that
|
||
// contains the indices of the pairs that can be moved to the head of
|
||
// the memory.
|
||
template <algorithm algo, class T>
|
||
memory<T>
|
||
initial_memory_of(const const_twa_graph_ptr &sub_aut,
|
||
const std::vector<acc_cond::rs_pair> &pairs,
|
||
std::vector<std::map<memory<T>, memory<T>>> &relations)
|
||
{
|
||
unsigned init_state = sub_aut->get_init_state_number();
|
||
if constexpr (algo == algorithm::CAR)
|
||
{
|
||
unsigned max_set = sub_aut->get_acceptance().used_sets().max_set();
|
||
memory<T> values(max_set);
|
||
std::iota(values.begin(), values.end(), 0);
|
||
if (opt_.force_order)
|
||
apply_move_heuristic(init_state, values, max_set, relations);
|
||
return values;
|
||
}
|
||
else if constexpr (algo == algorithm::TAR)
|
||
{
|
||
if (UINT_MAX < sub_aut->num_edges())
|
||
{
|
||
throw std::runtime_error("Too many edges for TAR");
|
||
}
|
||
const auto &ev = sub_aut->edge_vector();
|
||
const auto ev_size = ev.size();
|
||
memory<T> values(ev_size - 1);
|
||
// 0 is not an edge number
|
||
std::iota(values.begin(), values.end(), 1);
|
||
if (opt_.force_order && sub_aut->num_states() > 1)
|
||
{
|
||
unsigned free_pos = 0;
|
||
// If a transition goes to state, it is at the head of the memory.
|
||
for (unsigned i = 1; i < ev_size; ++i)
|
||
if (ev[i].dst == init_state)
|
||
{
|
||
std::swap(values[i - 1], values[free_pos]);
|
||
++free_pos;
|
||
}
|
||
}
|
||
return values;
|
||
}
|
||
else
|
||
{
|
||
static_assert(algo == IAR_RABIN || algo == IAR_STREETT);
|
||
memory<T> values(pairs.size());
|
||
std::iota(values.begin(), values.end(), 0);
|
||
if (opt_.force_order)
|
||
apply_move_heuristic(init_state, values, values.size(), relations);
|
||
return values;
|
||
}
|
||
}
|
||
|
||
// LAR
|
||
algorithm
|
||
choose_lar(const acc_cond &scc_condition,
|
||
std::vector<acc_cond::rs_pair> &pairs,
|
||
const unsigned num_edges)
|
||
{
|
||
std::vector<acc_cond::rs_pair> pairs1, pairs2;
|
||
bool is_rabin_like = scc_condition.is_rabin_like(pairs1);
|
||
bool is_streett_like = scc_condition.is_streett_like(pairs2);
|
||
// If we cannot apply IAR and TAR and CAR are not used
|
||
if ((!(is_rabin_like || is_streett_like) || !opt_.iar)
|
||
&& !(opt_.car || opt_.tar))
|
||
throw std::runtime_error("to_parity needs CAR or TAR to process "
|
||
"a condition that is not a Rabin or Streett "
|
||
"condition or if IAR is not enabled");
|
||
remove_duplicates(pairs1);
|
||
remove_duplicates(pairs2);
|
||
unsigned num_col = scc_condition.num_sets();
|
||
|
||
auto num_pairs1 = (opt_.iar && is_streett_like) ? pairs2.size() : -1UL;
|
||
auto num_pairs2 = (opt_.iar && is_rabin_like) ? pairs1.size() : -1UL;
|
||
|
||
// In practice, if the number of pairs is bigger than the number of
|
||
// colors, it will create a color greater than SPOT_MAX_ACCSETS, so
|
||
// we don't consider that it is a Rabin condition.
|
||
// In this case, if CAR or TAR is not used, it will throw a Runtime
|
||
// Error.
|
||
|
||
bool iar_overflow = false;
|
||
if ((num_pairs1 > MAX_MEM_ELEM) && (num_pairs2 > MAX_MEM_ELEM))
|
||
{
|
||
num_pairs1 = num_pairs2 = -1U;
|
||
iar_overflow = true;
|
||
}
|
||
|
||
const std::vector<unsigned long>
|
||
number_elements =
|
||
{
|
||
(opt_.iar && is_streett_like) ? pairs2.size() : -1UL,
|
||
(opt_.iar && is_rabin_like) ? pairs1.size() : -1UL,
|
||
opt_.car ? num_col : -1UL,
|
||
opt_.tar ? num_edges : -1UL};
|
||
constexpr std::array<algorithm, 4> algos = {IAR_STREETT, IAR_RABIN, CAR,
|
||
TAR};
|
||
int min_pos = std::distance(number_elements.begin(),
|
||
std::min_element(number_elements.begin(),
|
||
number_elements.end()));
|
||
|
||
if (number_elements[min_pos] == -1U && iar_overflow)
|
||
throw std::runtime_error(
|
||
"Too many Rabin/Streett pairs, try to increase SPOT_MAX_ACCSETS");
|
||
algorithm algo = algos[min_pos];
|
||
if (algo == IAR_RABIN)
|
||
pairs = pairs1;
|
||
else if (algo == IAR_STREETT)
|
||
pairs = pairs2;
|
||
return algo;
|
||
}
|
||
|
||
// Remove duplicates in pairs without changing the order.
|
||
static void
|
||
remove_duplicates(std::vector<acc_cond::rs_pair> &pairs)
|
||
{
|
||
std::vector<acc_cond::rs_pair> res;
|
||
res.reserve(pairs.size());
|
||
for (auto &elem : pairs)
|
||
if (std::find(res.begin(), res.end(), elem) == res.end())
|
||
res.emplace_back(elem);
|
||
pairs = res;
|
||
}
|
||
|
||
template <algorithm algo>
|
||
acc_cond::mark_t
|
||
fin(const std::vector<acc_cond::rs_pair> &pairs, unsigned k)
|
||
{
|
||
static_assert(algo == IAR_RABIN || algo == IAR_STREETT);
|
||
if constexpr (algo == IAR_RABIN)
|
||
return pairs[k].fin;
|
||
else
|
||
return pairs[k].inf;
|
||
}
|
||
|
||
template <algorithm algo>
|
||
acc_cond::mark_t
|
||
inf(const std::vector<acc_cond::rs_pair> &pairs, unsigned k)
|
||
{
|
||
static_assert(algo == IAR_RABIN || algo == IAR_STREETT);
|
||
if constexpr (algo == IAR_RABIN)
|
||
return pairs[k].inf;
|
||
else
|
||
return pairs[k].fin;
|
||
}
|
||
|
||
template <algorithm algo>
|
||
std::vector<std::map<memory<memory_type>, memory<memory_type>>>
|
||
find_relations(const const_twa_graph_ptr &sub_aut,
|
||
const std::vector<acc_cond::rs_pair> &pairs,
|
||
const std::set<unsigned> &pairs_indices)
|
||
{
|
||
static_assert(algo == IAR_RABIN || algo == IAR_STREETT || algo == CAR);
|
||
const unsigned sub_aut_num_states = sub_aut->num_states();
|
||
// Set of memory elements that can be at the head of the memory for
|
||
// a given state.
|
||
std::vector<std::set<memory<memory_type>>> incomem(sub_aut_num_states);
|
||
// Add a mark with all colors/pairs to deal with the order of the
|
||
// original state
|
||
if constexpr (algo == algorithm::CAR)
|
||
{
|
||
auto ms = sub_aut->get_acceptance().used_sets().max_set();
|
||
memory<memory_type> m(ms);
|
||
std::iota(m.begin(), m.end(), 0);
|
||
incomem[sub_aut->get_init_state_number()].insert(std::move(m));
|
||
}
|
||
else if constexpr (algo == IAR_RABIN || algo == IAR_STREETT)
|
||
{
|
||
memory<memory_type> m(pairs_indices.begin(), pairs_indices.end());
|
||
incomem[sub_aut->get_init_state_number()].insert(std::move(m));
|
||
}
|
||
|
||
for (auto &e : sub_aut->edges())
|
||
{
|
||
auto e_sets = e.acc.sets();
|
||
if constexpr (algo == algorithm::CAR)
|
||
incomem[e.dst].insert({e_sets.begin(), e_sets.end()});
|
||
// IAR
|
||
else
|
||
{
|
||
memory<memory_type> parti;
|
||
for (unsigned k : pairs_indices)
|
||
if (e.acc & fin<algo>(pairs, k))
|
||
parti.push_back(k);
|
||
incomem[e.dst].insert(parti);
|
||
}
|
||
}
|
||
std::vector<std::map<memory<memory_type>, memory<memory_type>>> res;
|
||
res.reserve(sub_aut_num_states);
|
||
for (unsigned i = 0; i < sub_aut_num_states; ++i)
|
||
{
|
||
std::map<memory<memory_type>, memory<memory_type>> ma;
|
||
// Memory incoming to state i.
|
||
std::vector<memory<memory_type>> elem(incomem[i].begin(),
|
||
incomem[i].end());
|
||
relation rel(elem);
|
||
for (auto &x : rel.labels_)
|
||
ma.insert({x, rel.find_order(x)});
|
||
res.emplace_back(ma);
|
||
}
|
||
return res;
|
||
}
|
||
|
||
void
|
||
apply_move_heuristic(unsigned state, memory<memory_type> &m,
|
||
unsigned nb_seen,
|
||
std::vector<std::map<
|
||
memory<memory_type>,
|
||
memory<memory_type>>> &relations)
|
||
{
|
||
// If we move 0 or 1 color we cannot change the order
|
||
if (nb_seen < 2)
|
||
return;
|
||
memory<memory_type> seen{m.begin(), m.begin() + nb_seen};
|
||
const auto &new_prefix = relations[state][seen];
|
||
|
||
unsigned new_prefix_size = new_prefix.size();
|
||
for (unsigned i = 0; i < new_prefix_size; ++i)
|
||
m[i] = new_prefix[i];
|
||
}
|
||
|
||
template <algorithm algo, class T>
|
||
void
|
||
find_new_memory(unsigned state, memory<T> &m, unsigned edge_number,
|
||
const acc_cond::mark_t &colors,
|
||
const std::vector<acc_cond::rs_pair> &pairs,
|
||
const std::set<unsigned> &pairs_indices,
|
||
unsigned &nb_seen,
|
||
unsigned &h,
|
||
std::vector<std::map<memory<T>, memory<T>>> &relations)
|
||
{
|
||
if constexpr (algo == TAR)
|
||
{
|
||
(void)state;
|
||
auto pos = std::find(m.begin(), m.end(), edge_number);
|
||
assert(pos != m.end());
|
||
h = std::distance(m.begin(), pos);
|
||
std::rotate(m.begin(), pos, pos + 1);
|
||
}
|
||
else if constexpr (algo == CAR)
|
||
{
|
||
(void)edge_number;
|
||
for (auto k : colors.sets())
|
||
{
|
||
auto it = std::find(m.begin(), m.end(), k);
|
||
// A color can exist in the automaton but not in the condition.
|
||
if (it != m.end())
|
||
{
|
||
h = std::max(h, (unsigned)(it - m.begin()) + 1);
|
||
std::rotate(m.begin(), it, it + 1);
|
||
++nb_seen;
|
||
}
|
||
}
|
||
if (opt_.force_order)
|
||
{
|
||
// apply_move_heuristic needs an increasing list of values
|
||
std::reverse(m.begin(), m.begin() + nb_seen);
|
||
apply_move_heuristic(state, m, nb_seen, relations);
|
||
}
|
||
}
|
||
else if constexpr (algo == IAR_RABIN || algo == IAR_STREETT)
|
||
{
|
||
(void)edge_number;
|
||
for (auto k = pairs_indices.rbegin(); k != pairs_indices.rend(); ++k)
|
||
if (colors & fin<algo>(pairs, *k))
|
||
{
|
||
++nb_seen;
|
||
auto it = std::find(m.begin(), m.end(), *k);
|
||
assert(it != m.end());
|
||
// move the pair in front of the permutation
|
||
std::rotate(m.begin(), it, it + 1);
|
||
}
|
||
if (opt_.force_order)
|
||
{
|
||
// As with CAR, in relation the partial memory is sorted. That is
|
||
// why the previous loop use a reverse iterator.
|
||
assert(std::is_sorted(m.begin(), m.begin() + nb_seen));
|
||
apply_move_heuristic(state, m, nb_seen, relations);
|
||
}
|
||
}
|
||
}
|
||
|
||
template <algorithm algo, typename T>
|
||
void
|
||
compute_new_color_lar(const const_twa_graph_ptr &sub_aut,
|
||
const memory<T> ¤t_mem,
|
||
const memory<T> &new_perm,
|
||
unsigned &h,
|
||
const acc_cond::mark_t &edge_colors,
|
||
acc_cond::mark_t &acc,
|
||
const std::vector<acc_cond::rs_pair> &pairs,
|
||
robin_hood::unordered_map<acc_cond::mark_t, bool>&
|
||
acc_cache)
|
||
{
|
||
// This function should not be called with algo ∉ [CAR, IAR, TAR].
|
||
static_assert(algo == CAR || algo == IAR_RABIN || algo == IAR_STREETT
|
||
|| algo == TAR);
|
||
assert(!acc);
|
||
auto sub_aut_cond = sub_aut->acc();
|
||
if constexpr (algo == CAR)
|
||
{
|
||
acc_cond::mark_t m(new_perm.begin(), new_perm.begin() + h);
|
||
auto cc = acc_cache.find(m);
|
||
bool rej;
|
||
if (cc != acc_cache.end())
|
||
rej = cc->second;
|
||
else
|
||
{
|
||
rej = !sub_aut_cond.accepting(m);
|
||
acc_cache.insert({m, rej});
|
||
}
|
||
unsigned value = 2 * h + rej - 1;
|
||
if (value != -1U)
|
||
assign_color(acc, value);
|
||
return;
|
||
}
|
||
else if constexpr (algo == TAR)
|
||
{
|
||
auto &edge_vector = sub_aut->edge_vector();
|
||
acc_cond::mark_t acc_seen {};
|
||
for (unsigned i = 0; i <= h; ++i)
|
||
acc_seen |= edge_vector[new_perm[i]].acc;
|
||
|
||
auto cc = acc_cache.find(acc_seen);
|
||
bool rej;
|
||
if (cc != acc_cache.end())
|
||
rej = cc->second;
|
||
else
|
||
{
|
||
rej = !sub_aut_cond.accepting(acc_seen);
|
||
acc_cache.insert({acc_seen, rej});
|
||
}
|
||
|
||
unsigned acc_col = 2 * h + rej - 1;
|
||
if (acc_col != -1U)
|
||
assign_color(acc, acc_col);
|
||
}
|
||
else
|
||
{
|
||
// IAR_RABIN produces a parity max even condition. If res_
|
||
// is parity max odd, we add 1 to a transition to produce a parity max
|
||
// odd automaton.
|
||
unsigned delta_acc = ((algo == IAR_RABIN) && is_odd_) - 1;
|
||
|
||
unsigned maxint = -1U;
|
||
for (int k = current_mem.size() - 1; k >= 0; --k)
|
||
{
|
||
unsigned pk = current_mem[k];
|
||
|
||
if (!inf<algo>(pairs, pk) || (edge_colors
|
||
& (pairs[pk].fin | pairs[pk].inf)))
|
||
{
|
||
maxint = k;
|
||
break;
|
||
}
|
||
}
|
||
|
||
unsigned value;
|
||
if (maxint == -1U)
|
||
value = delta_acc;
|
||
else if (edge_colors & fin<algo>(pairs, current_mem[maxint]))
|
||
value = 2 * maxint + 2 + delta_acc;
|
||
else
|
||
value = 2 * maxint + 1 + delta_acc;
|
||
|
||
if (value != -1U)
|
||
assign_color(acc, value);
|
||
}
|
||
}
|
||
|
||
void
|
||
change_to_odd()
|
||
{
|
||
if (is_odd_)
|
||
return;
|
||
is_odd_ = true;
|
||
// We can reduce if we don't have an edge without color.
|
||
bool can_reduce = (min_color_used_ != -1U) && (min_color_used_ != 0);
|
||
int shift;
|
||
|
||
if (can_reduce)
|
||
shift = - (min_color_used_ | 1);
|
||
else
|
||
shift = 1;
|
||
|
||
// If we cannot decrease and we already the the maximum color, we don't
|
||
// have to try. Constructs a mark_t to avoid to make report_too_many_sets
|
||
// public.
|
||
if (!can_reduce && max_color_used_ + shift >= MAX_ACCSETS)
|
||
acc_cond::mark_t {SPOT_MAX_ACCSETS};
|
||
max_color_used_ += shift;
|
||
if (min_color_used_ < -1U)
|
||
min_color_used_ += shift;
|
||
for (auto &e : res_->edges())
|
||
{
|
||
auto new_val = e.acc.max_set() - 1 + shift;
|
||
if (new_val != -1U)
|
||
e.acc = { new_val };
|
||
else
|
||
e.acc = {};
|
||
}
|
||
}
|
||
|
||
template <algorithm algo, typename T>
|
||
void
|
||
apply_lar(twa_graph_ptr &sub_aut,
|
||
const std::vector<acc_cond::rs_pair> &pairs)
|
||
{
|
||
if constexpr (algo != IAR_RABIN)
|
||
change_to_odd();
|
||
// avoids to call LAR if there is one color/pair/transition.
|
||
// LAR can work with this kind of condition but some optimizations
|
||
// like searching an existing state suppose that there is at least
|
||
// one element.
|
||
if ((algo == CAR && sub_aut->acc().num_sets() == 0)
|
||
|| ((algo == IAR_RABIN || algo == IAR_STREETT) && pairs.empty())
|
||
|| (algo == TAR && sub_aut->num_edges() == 0))
|
||
{
|
||
bool need_col = sub_aut->acc().is_t() != is_odd_;
|
||
auto col_fun = [&](const twa_graph::edge_storage_t &)
|
||
{
|
||
return need_col ? acc_cond::mark_t{0} : acc_cond::mark_t{};
|
||
};
|
||
apply_copy_general(sub_aut, col_fun, algo);
|
||
return;
|
||
}
|
||
// We sometimes need to have a list of the states
|
||
// of res_ constructed by this call to apply_lar.
|
||
const bool use_bscc = opt_.bscc;
|
||
const bool use_last_post_process = opt_.use_last_post_process;
|
||
constexpr bool is_tar = algo == TAR;
|
||
const bool need_tree = !is_tar
|
||
&& (opt_.search_ex || use_last_post_process);
|
||
const bool need_state_list = use_last_post_process || use_bscc;
|
||
const bool is_dfs = opt_.lar_dfs;
|
||
// state_2_lar adapts add_new_state such that depending on the
|
||
// value of use_last in get_compatible_state, we will be able
|
||
// to find a compatible state faster.
|
||
state_2_lar::memory_order order;
|
||
if (!opt_.use_last)
|
||
{
|
||
if (opt_.use_last_post_process)
|
||
order = state_2_lar::memory_order::BOTH;
|
||
else
|
||
order = state_2_lar::memory_order::ONLY_OLDEST;
|
||
}
|
||
else
|
||
order = state_2_lar::memory_order::ONLY_NEWEST;
|
||
state_2_lar s2l;
|
||
if (need_tree)
|
||
s2l.init(sub_aut->num_states(), order);
|
||
std::vector<unsigned> states_scc_res;
|
||
if (need_state_list)
|
||
states_scc_res.reserve(sub_aut->num_states());
|
||
auto init =
|
||
sub_aut->get_named_prop<std::vector<unsigned>>("original-states");
|
||
|
||
if (opt_.propagate_col)
|
||
propagate_marks_here(sub_aut);
|
||
|
||
auto init_state = sub_aut->get_init_state_number();
|
||
robin_hood::unordered_map<to_parity_state<T>,
|
||
unsigned, to_parity_hash<T>> ps_2_num;
|
||
unsigned lb_size;
|
||
if constexpr (algo == TAR)
|
||
lb_size = aut_->num_edges();
|
||
else if constexpr (algo == CAR)
|
||
lb_size = aut_->num_states() * aut_->acc().num_sets();
|
||
else
|
||
lb_size = aut_->num_states() * pairs.size();
|
||
// num_2_ps maps a state of the result to a parity_state. As this function
|
||
// does not always create the first state, we need to add
|
||
// "- nb_states_before" to get a value.
|
||
const unsigned nb_states_before = res_->num_states();
|
||
std::vector<to_parity_state<T>> num_2_ps;
|
||
// At least one copy of each state will be created.
|
||
num_2_ps.reserve(lb_size + num_2_ps.size());
|
||
ps_2_num.reserve(lb_size + num_2_ps.size());
|
||
|
||
std::deque<unsigned> todo;
|
||
// return a pair new_state, is_new such that
|
||
// ps is associated to the state new_state in res_
|
||
// and is_new is true if a new state was created by
|
||
// get_state
|
||
// We store 2 unsigned in a long long.
|
||
static_assert(sizeof(long long) >= 2 * sizeof(unsigned));
|
||
robin_hood::unordered_map<long long, unsigned>* edge_cache = nullptr;
|
||
if (!use_last_post_process)
|
||
{
|
||
edge_cache = new robin_hood::unordered_map<long long, unsigned>();
|
||
edge_cache->reserve(sub_aut->num_edges());
|
||
}
|
||
auto get_state = [&](const to_parity_state<T> &&ps) constexpr
|
||
{
|
||
auto it = ps_2_num.find(ps);
|
||
if (it == ps_2_num.end())
|
||
{
|
||
unsigned nb = add_res_state(algo, ps);
|
||
ps_2_num[ps] = nb;
|
||
assert(nb == num_2_ps.size() + nb_states_before);
|
||
num_2_ps.emplace_back(ps);
|
||
todo.push_back(nb);
|
||
if (need_state_list)
|
||
states_scc_res.push_back(nb);
|
||
return std::pair<unsigned, bool>{nb, true};
|
||
}
|
||
return std::pair<unsigned, bool>{it->second, false};
|
||
};
|
||
|
||
std::set<unsigned> pairs_indices;
|
||
std::vector<std::map<memory<T>, memory<T>>> relations;
|
||
if constexpr (algo == IAR_STREETT || algo == IAR_RABIN)
|
||
{
|
||
const auto num_pairs = pairs.size();
|
||
for (unsigned k = 0; k < num_pairs; ++k)
|
||
if (fin<algo>(pairs, k))
|
||
pairs_indices.insert(k);
|
||
}
|
||
|
||
if constexpr (algo != TAR)
|
||
if (opt_.force_order)
|
||
relations = find_relations<algo>(sub_aut, pairs, pairs_indices);
|
||
|
||
auto m = initial_memory_of<algo>(sub_aut, pairs, relations);
|
||
|
||
assert(init);
|
||
auto init_res = get_state({(*init)[init_state], init_state, m}).first;
|
||
// A path is added when it is a destination. That is why we need to
|
||
// add the initial state.
|
||
unsigned nb_edges_before = res_->num_edges();
|
||
std::vector<unsigned> edge_to_seen_nb;
|
||
if (use_last_post_process && algo != TAR)
|
||
edge_to_seen_nb.reserve(sub_aut->num_edges());
|
||
if constexpr(!is_tar)
|
||
if (need_tree)
|
||
s2l.add_new_path(init_state, m, init_res, 0);
|
||
|
||
robin_hood::unordered_map<acc_cond::mark_t, bool> acc_cache;
|
||
// Main loop
|
||
while (!todo.empty())
|
||
{
|
||
if (edge_cache)
|
||
edge_cache->clear();
|
||
// If we want to process the most recent state of the result, we
|
||
// take the last value
|
||
unsigned res_current = is_dfs ? todo.back() : todo.front();
|
||
unsigned res_index = res_current - nb_states_before;
|
||
const auto ¤t_ps = num_2_ps[res_index];
|
||
const auto current_mem = current_ps.mem;
|
||
if (is_dfs)
|
||
todo.pop_back();
|
||
else
|
||
todo.pop_front();
|
||
|
||
// For each edge leaving the state corresponding to res_state in sub_aut
|
||
for (auto &e : sub_aut->out(current_ps.state_scc))
|
||
{
|
||
// We create a new memory and update it
|
||
memory<T> mem(current_mem);
|
||
unsigned nb_seen = 0,
|
||
h = 0;
|
||
find_new_memory<algo>(e.dst, mem, sub_aut->edge_number(e), e.acc,
|
||
pairs, pairs_indices, nb_seen, h, relations);
|
||
// Now we try to find a way to move the elements and obtain an
|
||
// existing memory.
|
||
unsigned res_dst = -1U;
|
||
if constexpr (algo != TAR)
|
||
if (opt_.search_ex)
|
||
res_dst = s2l.get_compatible_state(e.dst, mem, nb_seen,
|
||
opt_.use_last);
|
||
// If it doesn't exist, we create a new state…
|
||
if (res_dst == -1U)
|
||
{
|
||
auto gs = get_state({(*init)[e.dst], e.dst, mem});
|
||
res_dst = gs.first;
|
||
// And add it to the "tree" used to find a compatible state
|
||
if constexpr (!is_tar)
|
||
{
|
||
if (need_tree && gs.second)
|
||
s2l.add_new_path(e.dst, mem, res_dst, nb_seen);
|
||
}
|
||
}
|
||
|
||
// We compute the color assigned to the new edge.
|
||
acc_cond::mark_t new_edge_color{};
|
||
compute_new_color_lar<algo, T>(sub_aut, current_mem, mem, h, e.acc,
|
||
new_edge_color, pairs, acc_cache);
|
||
|
||
// As we can assign a new destination later when
|
||
// use_last_post_process is true, we cannot try to find a compatible
|
||
// edge.
|
||
auto edge_res_num = add_res_edge(res_current, res_dst,
|
||
new_edge_color, e.cond,
|
||
!use_last_post_process,
|
||
edge_cache);
|
||
(void) edge_res_num;
|
||
// We have to remember how many colors were seen if we do a post
|
||
// processing
|
||
if constexpr (algo != TAR)
|
||
if (use_last_post_process)
|
||
{
|
||
assert(edge_res_num ==
|
||
edge_to_seen_nb.size() + nb_edges_before + 1);
|
||
edge_to_seen_nb.push_back(nb_seen);
|
||
}
|
||
}
|
||
}
|
||
|
||
// We used the most recent compatible state but perhaps that another
|
||
// state was created after. We do a new search. As TAR always moves one
|
||
// element we don't need it.
|
||
if constexpr (algo != TAR)
|
||
if (use_last_post_process)
|
||
{
|
||
for (auto &res_state : states_scc_res)
|
||
for (auto &e : res_->out(res_state))
|
||
{
|
||
auto e_dst = e.dst;
|
||
if (e.src == e_dst)
|
||
continue;
|
||
auto edge_num = res_->edge_number(e);
|
||
const auto &ps = num_2_ps[e_dst - nb_states_before];
|
||
unsigned seen_nb =
|
||
edge_to_seen_nb[edge_num - nb_edges_before - 1];
|
||
assert(seen_nb < SPOT_MAX_ACCSETS);
|
||
auto new_dst = s2l.get_compatible_state(ps.state_scc, ps.mem,
|
||
seen_nb, true);
|
||
if (new_dst != e_dst)
|
||
{
|
||
assert(new_dst != -1U);
|
||
need_purge_ = true;
|
||
e.dst = new_dst;
|
||
}
|
||
}
|
||
}
|
||
if (use_bscc)
|
||
{
|
||
// Contrary to the (old) implementation of IAR, adding an edge between
|
||
// 2 SCCs of the result is the last thing done. It means that
|
||
// we don't need to use a filter when we compute the BSCC.
|
||
// A state s is in the BSCC if scc_of(s) is 0.
|
||
scc_info sub_scc(res_, init_res, nullptr, nullptr,
|
||
scc_info_options::NONE);
|
||
if (sub_scc.scc_count() > 1)
|
||
{
|
||
need_purge_ = true;
|
||
for (auto &state_produced : states_scc_res)
|
||
if (sub_scc.scc_of(state_produced) == 0)
|
||
state_to_res_[res_to_aut_[state_produced]] = state_produced;
|
||
}
|
||
}
|
||
delete edge_cache;
|
||
}
|
||
|
||
void
|
||
link_sccs()
|
||
{
|
||
if (si_.scc_count() > 1)
|
||
{
|
||
const unsigned res_num_states = res_->num_states();
|
||
for (unsigned i = 0; i < res_num_states; ++i)
|
||
{
|
||
auto aut_i = res_to_aut_[i];
|
||
auto aut_i_scc = si_.scc_of(aut_i);
|
||
for (auto &e : aut_->out(aut_i))
|
||
if (aut_i_scc != si_.scc_of(e.dst))
|
||
{
|
||
auto e_dst_repr = state_to_res_[e.dst];
|
||
add_res_edge(i, e_dst_repr, {}, e.cond);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
bool
|
||
try_parity_equivalence(const zielonka_tree &tree,
|
||
const twa_graph_ptr &sub_aut)
|
||
{
|
||
if (tree.has_parity_shape())
|
||
{
|
||
bool first_is_accepting = tree.is_even();
|
||
// A vector that stores the difference between 2 levels.
|
||
std::vector<acc_cond::mark_t> colors_diff;
|
||
auto &tree_nodes = tree.nodes_;
|
||
// Supposes that the index of the root is 0.
|
||
unsigned current_index = 0;
|
||
auto current_node = tree_nodes[current_index];
|
||
// While the current node has a child
|
||
while (current_node.first_child != 0)
|
||
{
|
||
auto child_index = current_node.first_child;
|
||
auto child = tree_nodes[child_index];
|
||
acc_cond::mark_t diff = current_node.colors - child.colors;
|
||
colors_diff.emplace_back(diff);
|
||
current_node = child;
|
||
}
|
||
// We have to deal with the edge between the last node and ∅.
|
||
bool is_empty_accepting = sub_aut->acc().accepting({});
|
||
bool is_current_accepting = (current_node.level % 2) != tree.is_even();
|
||
if (is_empty_accepting != is_current_accepting)
|
||
colors_diff.emplace_back(current_node.colors);
|
||
// + 1 as we need to know which value should be given to an uncolored
|
||
// edge.
|
||
std::vector<unsigned> new_colors(
|
||
sub_aut->get_acceptance().used_sets().max_set() + 1, -1U);
|
||
unsigned current_col = colors_diff.size() - 1;
|
||
for (auto &diff : colors_diff)
|
||
{
|
||
for (auto col : diff.sets())
|
||
new_colors[col + 1] = current_col;
|
||
--current_col;
|
||
}
|
||
bool is_max_even = first_is_accepting == (colors_diff.size() % 2);
|
||
if (!is_max_even)
|
||
change_to_odd();
|
||
|
||
bool is_even_in_odd_world = is_odd_ && is_max_even;
|
||
if (is_even_in_odd_world)
|
||
for (auto &x : new_colors)
|
||
++x;
|
||
apply_copy(sub_aut, new_colors, PARITY_EQUIV);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool
|
||
try_parity_prefix(const zielonka_tree &tree, const twa_graph_ptr &sub_aut)
|
||
{
|
||
unsigned index = 0;
|
||
auto current = tree.nodes_[index];
|
||
std::vector<acc_cond::mark_t> prefixes;
|
||
bool first_is_accepting = tree.is_even();
|
||
|
||
acc_cond::mark_t removed_cols{};
|
||
auto has_one_child = [&](const auto node) constexpr
|
||
{
|
||
auto fc = node.first_child;
|
||
return tree.nodes_[fc].next_sibling == fc;
|
||
};
|
||
while (has_one_child(current))
|
||
{
|
||
auto child = tree.nodes_[current.first_child];
|
||
acc_cond::mark_t diff{};
|
||
const bool is_leaf = current.first_child == 0;
|
||
if (is_leaf)
|
||
diff = current.colors;
|
||
else
|
||
diff = current.colors - child.colors;
|
||
prefixes.emplace_back(diff);
|
||
removed_cols |= diff;
|
||
if (is_leaf)
|
||
break;
|
||
current = child;
|
||
}
|
||
if (prefixes.empty())
|
||
return false;
|
||
|
||
if (opt_.datas)
|
||
algo_used_ |= algorithm::PARITY_PREFIX;
|
||
|
||
// As we want to remove the prefix we need to remove it from the
|
||
// condition. As an unused color is not always removed (acc_clean false),
|
||
// we do it here.
|
||
auto used_cols = sub_aut->get_acceptance().used_sets() - removed_cols;
|
||
auto new_cond = sub_aut->acc().restrict_to(used_cols);
|
||
scc_info_to_parity sub(sub_aut, removed_cols);
|
||
// The recursive call will add some informations to help
|
||
// to add missing edges
|
||
state_to_nums_ =
|
||
new std::vector<std::vector<unsigned>>(aut_->num_states());
|
||
opt_.parity_prefix = false;
|
||
bool old_pp_gen = opt_.parity_prefix_general;
|
||
opt_.parity_prefix_general = false;
|
||
|
||
unsigned max_scc_color_rec = max_color_scc_;
|
||
for (auto x : sub.split_aut({removed_cols}))
|
||
{
|
||
x->set_acceptance(new_cond);
|
||
process_scc(x, algorithm::PARITY_PREFIX);
|
||
max_scc_color_rec = std::max(max_scc_color_rec, max_color_scc_);
|
||
}
|
||
opt_.parity_prefix = true;
|
||
opt_.parity_prefix_general = old_pp_gen;
|
||
|
||
assert(max_scc_color_rec > 0);
|
||
bool max_used_is_accepting = ((max_scc_color_rec - 1) % 2) == is_odd_;
|
||
bool last_prefix_acc = (prefixes.size() % 2) != first_is_accepting;
|
||
|
||
unsigned m = prefixes.size() + (max_used_is_accepting != last_prefix_acc)
|
||
+ max_scc_color_rec - 1;
|
||
auto sub_aut_orig =
|
||
sub_aut->get_named_prop<std::vector<unsigned>>("original-states");
|
||
assert(sub_aut_orig);
|
||
for (auto &e : sub_aut->edges())
|
||
if (e.acc & removed_cols)
|
||
{
|
||
auto el = std::find_if(prefixes.begin(), prefixes.end(),
|
||
[&](acc_cond::mark_t &x)
|
||
{ return x & e.acc; });
|
||
assert(el != prefixes.end());
|
||
unsigned pos = std::distance(prefixes.begin(), el);
|
||
const unsigned col = m - pos;
|
||
// As it is a parity prefix we should never get a lower value than
|
||
// the color recursively produced.
|
||
assert(col >= max_scc_color_rec);
|
||
unsigned dst = state_to_res_[(*sub_aut_orig)[e.dst]];
|
||
for (auto src : (*state_to_nums_)[(*sub_aut_orig)[e.src]])
|
||
if (col != -1U)
|
||
add_res_edge(src, dst, {col}, e.cond);
|
||
else
|
||
add_res_edge(src, dst, {}, e.cond);
|
||
}
|
||
// As when we need to use link_scc, a set of edges that link 2 SCC
|
||
// need to be added and don't need to have a color.
|
||
else if (sub.scc_of(e.src) != sub.scc_of(e.dst))
|
||
{
|
||
unsigned dst = state_to_res_[(*sub_aut_orig)[e.dst]];
|
||
for (auto src : (*state_to_nums_)[(*sub_aut_orig)[e.src]])
|
||
add_res_edge(src, dst, {}, e.cond);
|
||
}
|
||
delete state_to_nums_;
|
||
state_to_nums_ = nullptr;
|
||
|
||
return true;
|
||
}
|
||
|
||
bool
|
||
try_parity_prefix_general(twa_graph_ptr &sub_aut)
|
||
{
|
||
// This function should not be applied on an "empty" automaton as
|
||
// it must create an empty SCC with the algorithm NONE.
|
||
assert(sub_aut->num_edges() > 0);
|
||
static_assert((MAX_ACCSETS % 2) == 0,
|
||
"MAX_ACCSETS is supposed to be even");
|
||
std::vector<acc_cond::mark_t> res_colors;
|
||
std::vector<edge_status> status;
|
||
acc_cond new_cond;
|
||
bool was_able_to_color;
|
||
// Is the maximal color accepting?
|
||
bool start_inf = true;
|
||
cond_type_main_aux(sub_aut, cond_kind::INF_PARITY, false, status,
|
||
res_colors, new_cond, was_able_to_color,
|
||
SPOT_MAX_ACCSETS - 1);
|
||
// Otherwise we can try to find a rejecting transition as first step
|
||
if (!was_able_to_color)
|
||
{
|
||
cond_type_main_aux(sub_aut, cond_kind::FIN_PARITY, false, status,
|
||
res_colors, new_cond, was_able_to_color,
|
||
SPOT_MAX_ACCSETS - 1);
|
||
if (!was_able_to_color)
|
||
return false;
|
||
start_inf = false;
|
||
}
|
||
|
||
// If we have a parity-type automaton, it is just a copy.
|
||
if (std::find(status.begin(), status.end(), edge_status::IMPOSSIBLE)
|
||
== status.end())
|
||
{
|
||
std::vector<unsigned> res_cols;
|
||
res_cols.reserve(res_colors.size());
|
||
|
||
auto min_set =
|
||
std::min_element(res_colors.begin() + 1, res_colors.end())->max_set();
|
||
// Does the minimal color has the same parity than the maximal parity?
|
||
bool same_acceptance_min_max = (min_set % 2);
|
||
// Do we need to shift to match the parity of res_?
|
||
bool odd_shift = start_inf != is_odd_;
|
||
unsigned shift_col = min_set - (same_acceptance_min_max != odd_shift);
|
||
std::transform(res_colors.begin(), res_colors.end(),
|
||
std::back_inserter(res_cols), [&](auto &x)
|
||
{ return x.max_set() - 1 - shift_col; });
|
||
apply_copy_edge_index(sub_aut, res_cols,
|
||
algorithm::PARITY_PREFIX_GENERAL);
|
||
return true;
|
||
}
|
||
|
||
// At this moment, a prefix exists
|
||
auto& ev = sub_aut->edge_vector();
|
||
const auto ev_size = ev.size();
|
||
auto keep = std::shared_ptr<bitvect>(make_bitvect(ev_size));
|
||
const unsigned status_size = status.size();
|
||
for (unsigned i = 1; i < status_size; ++i)
|
||
if (status[i] == edge_status::IMPOSSIBLE)
|
||
keep->set(i);
|
||
else
|
||
keep->clear(i);
|
||
|
||
// Avoid recursive parity prefix
|
||
opt_.parity_prefix_general = false;
|
||
bool old_pp = opt_.parity_prefix;
|
||
opt_.parity_prefix = false;
|
||
|
||
unsigned max_scc_color_rec = max_color_scc_;
|
||
scc_info lower_scc(sub_aut, scc_info_options::TRACK_STATES);
|
||
scc_info_to_parity sub(lower_scc, keep);
|
||
state_to_nums_ =
|
||
new std::vector<std::vector<unsigned>>(aut_->num_states());
|
||
for (auto x : sub.split_aut(keep))
|
||
{
|
||
process_scc(x, algorithm::PARITY_PREFIX_GENERAL);
|
||
max_scc_color_rec = std::max(max_scc_color_rec, max_color_scc_);
|
||
}
|
||
|
||
// restore options
|
||
opt_.parity_prefix_general = true;
|
||
opt_.parity_prefix = old_pp;
|
||
|
||
assert(sub_aut->num_edges() > 0);
|
||
|
||
// Compute the minimal color used by parity prefix.
|
||
unsigned min_set_prefix = -2U;
|
||
for (unsigned i = 1; i < ev_size; ++i)
|
||
if (status[i] == edge_status::MARKED)
|
||
{
|
||
auto e_mark = res_colors[i].max_set();
|
||
if (min_set_prefix == -2U)
|
||
min_set_prefix = e_mark - 1;
|
||
else
|
||
min_set_prefix = std::min(min_set_prefix + 1, e_mark) - 1;
|
||
}
|
||
|
||
// At least one transition should be marked here.
|
||
assert(min_set_prefix != -2U);
|
||
|
||
// Reduce the colors used by parity_prefix.
|
||
const bool min_prefix_accepting = (min_set_prefix % 2) == start_inf;
|
||
// max_scc_color_rec has a value as the automaton is not parity-type,
|
||
// so there was a recursive paritisation
|
||
assert(max_scc_color_rec > 0);
|
||
const bool max_rec_accepting = ((max_scc_color_rec - 1) % 2) == is_odd_;
|
||
const bool same_prio = min_prefix_accepting == max_rec_accepting;
|
||
const unsigned delta =
|
||
min_set_prefix - (max_scc_color_rec + 1) - !same_prio;
|
||
|
||
auto sub_aut_orig =
|
||
sub_aut->get_named_prop<std::vector<unsigned>>("original-states");
|
||
assert(sub_aut_orig);
|
||
for (unsigned e_num = 1; e_num < ev_size; ++e_num)
|
||
{
|
||
auto& e = ev[e_num];
|
||
if (status[e_num] == edge_status::MARKED)
|
||
{
|
||
unsigned dst = state_to_res_[(*sub_aut_orig)[e.dst]];
|
||
for (auto src : (*state_to_nums_)[(*sub_aut_orig)[e.src]])
|
||
{
|
||
auto col = res_colors[e_num].max_set() - delta - 1;
|
||
if (col == -1U)
|
||
add_res_edge(src, dst, {}, e.cond);
|
||
else
|
||
add_res_edge(src, dst, {col}, e.cond);
|
||
}
|
||
}
|
||
}
|
||
|
||
delete state_to_nums_;
|
||
state_to_nums_ = nullptr;
|
||
|
||
return true;
|
||
}
|
||
|
||
bool
|
||
try_emptiness(const const_twa_graph_ptr &sub_aut, bool &tried)
|
||
{
|
||
tried = true;
|
||
if (generic_emptiness_check(sub_aut))
|
||
{
|
||
auto col_fun =
|
||
[col = is_odd_ ? acc_cond::mark_t{0} : acc_cond::mark_t{}]
|
||
(const twa_graph::edge_storage_t &) noexcept
|
||
{
|
||
return col;
|
||
};
|
||
apply_copy_general(sub_aut, col_fun, GENERIC_EMPTINESS);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool
|
||
try_rabin_to_buchi(twa_graph_ptr &sub_aut)
|
||
{
|
||
algorithm algo = RABIN_TO_BUCHI;
|
||
auto buch_aut = rabin_to_buchi_if_realizable(sub_aut);
|
||
if (buch_aut == nullptr)
|
||
{
|
||
algo = STREETT_TO_COBUCHI;
|
||
auto old_cond = sub_aut->get_acceptance();
|
||
sub_aut->set_acceptance(acc_cond(old_cond.complement()));
|
||
buch_aut = rabin_to_buchi_if_realizable(sub_aut);
|
||
sub_aut->set_acceptance(acc_cond(old_cond));
|
||
}
|
||
if (buch_aut != nullptr)
|
||
{
|
||
if (algo == STREETT_TO_COBUCHI)
|
||
change_to_odd();
|
||
unsigned shift = (algo == RABIN_TO_BUCHI) && is_odd_;
|
||
|
||
auto &buch_aut_ev = buch_aut->edge_vector();
|
||
// 0 is not an edge, so we assign -1;
|
||
std::vector<unsigned> colors;
|
||
colors.reserve(buch_aut_ev.size());
|
||
colors.push_back(-1U);
|
||
std::transform(
|
||
buch_aut_ev.begin() + 1, buch_aut_ev.end(),
|
||
std::back_inserter(colors),
|
||
[&](const twa_graph::edge_storage_t &e) {
|
||
return e.acc.max_set() - 1 + shift;
|
||
});
|
||
apply_copy_edge_index(sub_aut, colors, algo);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool
|
||
try_buchi_type(const twa_graph_ptr &sub_aut)
|
||
{
|
||
std::vector<edge_status> status;
|
||
std::vector<acc_cond::mark_t> res_colors;
|
||
acc_cond new_cond;
|
||
bool is_co_bu = false;
|
||
bool was_able_to_color;
|
||
if (!cond_type_main_aux(sub_aut, cond_kind::BUCHI, true, status,
|
||
res_colors, new_cond, was_able_to_color, 0))
|
||
{
|
||
is_co_bu = true;
|
||
if (!cond_type_main_aux(sub_aut, cond_kind::CO_BUCHI, true, status,
|
||
res_colors, new_cond, was_able_to_color, 0))
|
||
return false;
|
||
change_to_odd();
|
||
}
|
||
// Tests if all edges are colored or all edges are uncolored
|
||
auto [min, max] =
|
||
std::minmax_element(res_colors.begin() + 1, res_colors.end());
|
||
const bool one_color = min->max_set() == max->max_set();
|
||
const bool is_colored = min->max_set();
|
||
auto col_fun = [&](const twa_graph::edge_storage_t &edge)
|
||
{
|
||
// If there one color in the automaton, we can simplify.
|
||
if (one_color)
|
||
{
|
||
bool z = (is_colored && !is_odd_) || (!is_colored && is_odd_);
|
||
// When we do co-buchi, we reverse
|
||
if (is_co_bu)
|
||
z = !z;
|
||
return z ? acc_cond::mark_t{0} : acc_cond::mark_t{};
|
||
}
|
||
// Otherwise, copy the color
|
||
auto edge_number = sub_aut->edge_number(edge);
|
||
unsigned mc = res_colors[edge_number].max_set() - 1;
|
||
mc += (!is_co_bu && is_odd_);
|
||
if (mc == -1U)
|
||
return acc_cond::mark_t{};
|
||
return acc_cond::mark_t{mc};
|
||
};
|
||
apply_copy_general(sub_aut, col_fun, is_co_bu ? algorithm::CO_BUCHI_TYPE
|
||
: algorithm::BUCHI_TYPE);
|
||
return true;
|
||
}
|
||
|
||
bool
|
||
try_parity_type(const twa_graph_ptr &sub_aut)
|
||
{
|
||
std::vector<edge_status> status;
|
||
std::vector<acc_cond::mark_t> res_colors;
|
||
acc_cond new_cond;
|
||
bool was_able_to_color;
|
||
if (!cond_type_main_aux(sub_aut, cond_kind::INF_PARITY, true, status,
|
||
res_colors, new_cond, was_able_to_color,
|
||
SPOT_MAX_ACCSETS - 3))
|
||
{
|
||
if (!cond_type_main_aux(sub_aut, cond_kind::FIN_PARITY, true, status,
|
||
res_colors, new_cond, was_able_to_color,
|
||
SPOT_MAX_ACCSETS - 3))
|
||
return false;
|
||
}
|
||
bool is_max, is_odd;
|
||
new_cond.is_parity(is_max, is_odd);
|
||
auto min =
|
||
std::min_element(res_colors.begin() + 1, res_colors.end());
|
||
// cond_type_main_aux returns a parity max condition
|
||
assert(is_max);
|
||
auto col_fun =
|
||
[shift = (is_odd != is_odd_) - (min->max_set() + (min->max_set() % 2)),
|
||
&res_colors, &sub_aut]
|
||
(const twa_graph::edge_storage_t &edge)
|
||
{
|
||
auto edge_number = sub_aut->edge_number(edge);
|
||
unsigned mc = res_colors[edge_number].max_set() - 1;
|
||
mc += shift;
|
||
if (mc == -1U)
|
||
return acc_cond::mark_t{};
|
||
return acc_cond::mark_t{mc};
|
||
};
|
||
apply_copy_general(sub_aut, col_fun, PARITY_TYPE);
|
||
return true;
|
||
}
|
||
|
||
// Keeps the result of the partial degeneralization if it reduces the number
|
||
// of colors or it allows to apply IAR.
|
||
bool
|
||
keep_deg(const const_twa_graph_ptr &sub_aut, const const_twa_graph_ptr °)
|
||
{
|
||
if (!opt_.reduce_col_deg)
|
||
return true;
|
||
unsigned nb_col_orig = sub_aut->get_acceptance().used_sets().count();
|
||
|
||
if (deg->get_acceptance().used_sets().count() < nb_col_orig)
|
||
return true;
|
||
std::vector<acc_cond::rs_pair> pairs;
|
||
if (deg->acc().is_rabin_like(pairs))
|
||
{
|
||
remove_duplicates(pairs);
|
||
if (pairs.size() < nb_col_orig)
|
||
return true;
|
||
}
|
||
if (deg->acc().is_streett_like(pairs))
|
||
{
|
||
remove_duplicates(pairs);
|
||
if (pairs.size() < nb_col_orig)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// Process a SCC. If there is no edge in the automaton, a new state is
|
||
// created and we say (if pretty_print is true) that none_algo created
|
||
// this state.
|
||
void
|
||
process_scc(twa_graph_ptr &sub_aut,
|
||
const algorithm none_algo = algorithm::NONE)
|
||
{
|
||
// Init the maximal color produced when processing this SCC.
|
||
max_color_scc_ = 0;
|
||
// If the sub_automaton is "empty", we don't need to apply an algorithm.
|
||
if (sub_aut->num_edges() == 0)
|
||
{
|
||
apply_copy(sub_aut, {}, none_algo);
|
||
return;
|
||
}
|
||
|
||
bool tried_emptiness = false;
|
||
bool changed_structure = true;
|
||
while (true)
|
||
{
|
||
auto cond_before_simpl = sub_aut->acc();
|
||
if (opt_.acc_clean)
|
||
simplify_acceptance_here(sub_aut);
|
||
if (opt_.propagate_col)
|
||
{
|
||
propagate_marks_here(sub_aut);
|
||
if (opt_.acc_clean)
|
||
simplify_acceptance_here(sub_aut);
|
||
}
|
||
if (opt_.datas && sub_aut->acc() != cond_before_simpl)
|
||
algo_used_ |= algorithm::ACC_CLEAN;
|
||
|
||
if (opt_.parity_equiv || opt_.parity_prefix)
|
||
{
|
||
// If we don't try to find a parity prefix, we can stop
|
||
// to construct the tree when it has not parity shape.
|
||
zielonka_tree_options zopt = zielonka_tree_options::MERGE_SUBTREES
|
||
| zielonka_tree_options::CHECK_PARITY;
|
||
if (!opt_.parity_prefix)
|
||
zopt = zopt | zielonka_tree_options::ABORT_WRONG_SHAPE;
|
||
auto tree = zielonka_tree(sub_aut->acc(), zopt);
|
||
// If it is not parity shape, tree.nodes_ will be empty
|
||
if (tree.num_branches() != 0 && opt_.parity_equiv
|
||
&& try_parity_equivalence(tree, sub_aut))
|
||
return;
|
||
if (opt_.parity_prefix && try_parity_prefix(tree, sub_aut))
|
||
return;
|
||
}
|
||
|
||
if (changed_structure && opt_.parity_prefix_general
|
||
&& try_parity_prefix_general(sub_aut))
|
||
return;
|
||
|
||
if (opt_.generic_emptiness && !tried_emptiness
|
||
&& try_emptiness(sub_aut, tried_emptiness))
|
||
return;
|
||
|
||
// Buchi_type_to_buchi is more general that Rabin_to_buchi so
|
||
// we just call rabin_to_buchi if buchi_type_to_buchi is false.
|
||
if (!opt_.buchi_type_to_buchi && !opt_.parity_type_to_parity
|
||
&& opt_.rabin_to_buchi
|
||
&& try_rabin_to_buchi(sub_aut))
|
||
return;
|
||
|
||
// As parity_type_to_parity is stronger, we don't
|
||
// try if this option is used.
|
||
if (opt_.buchi_type_to_buchi && !opt_.parity_type_to_parity
|
||
&& try_buchi_type(sub_aut))
|
||
return;
|
||
|
||
// We don't do it if parity_prefix_general is true as on a parity-type
|
||
// automaton parity_prefix_general removes all the transitions and
|
||
// we also get a parity-type automaton.
|
||
if (!opt_.parity_prefix_general && opt_.parity_type_to_parity
|
||
&& try_parity_type(sub_aut))
|
||
return;
|
||
|
||
if (opt_.partial_degen
|
||
&& is_partially_degeneralizable(sub_aut, true, true))
|
||
{
|
||
auto deg = sub_aut;
|
||
std::vector<acc_cond::mark_t> forbid;
|
||
auto m = is_partially_degeneralizable(sub_aut, true, true, forbid);
|
||
bool changed = false;
|
||
while (m)
|
||
{
|
||
auto tmp = partial_degeneralize(deg, m);
|
||
simplify_acceptance_here(tmp);
|
||
if (keep_deg(deg, tmp))
|
||
{
|
||
algo_used_ |= algorithm::PARTIAL_DEGEN;
|
||
deg = tmp;
|
||
changed = true;
|
||
changed_structure = true;
|
||
}
|
||
else
|
||
forbid.emplace_back(m);
|
||
m = is_partially_degeneralizable(deg, true, true, forbid);
|
||
}
|
||
|
||
if (changed)
|
||
{
|
||
sub_aut = deg;
|
||
continue;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
if (opt_.use_generalized_rabin)
|
||
{
|
||
auto gen_rab = to_generalized_rabin(sub_aut);
|
||
// to_generalized_rabin does not propagate original-states.
|
||
auto sub_aut_orig =
|
||
sub_aut->get_named_prop<std::vector<unsigned>>("original-states");
|
||
assert(sub_aut_orig);
|
||
auto orig = new std::vector<unsigned>();
|
||
const auto sub_aut_num_states = sub_aut->num_states();
|
||
orig->reserve(sub_aut_num_states);
|
||
gen_rab->set_named_prop("original-states", orig);
|
||
for (unsigned i = 0; i < sub_aut_num_states; ++i)
|
||
orig->push_back((*sub_aut_orig)[i]);
|
||
sub_aut = partial_degeneralize(gen_rab);
|
||
}
|
||
std::vector<acc_cond::rs_pair> pairs;
|
||
algorithm algo = choose_lar(sub_aut->acc(), pairs, sub_aut->num_edges());
|
||
if (opt_.datas)
|
||
algo_used_ |= algo;
|
||
if (algo == CAR)
|
||
apply_lar<CAR, memory_type>(sub_aut, pairs);
|
||
else if (algo == IAR_STREETT)
|
||
apply_lar<IAR_STREETT, memory_type>(sub_aut, pairs);
|
||
else if (algo == IAR_RABIN)
|
||
apply_lar<IAR_RABIN, memory_type>(sub_aut, pairs);
|
||
else if (algo == TAR)
|
||
apply_lar<TAR, unsigned>(sub_aut, pairs);
|
||
else
|
||
SPOT_UNREACHABLE();
|
||
}
|
||
|
||
public:
|
||
twa_graph_ptr
|
||
run()
|
||
{
|
||
res_ = make_twa_graph(aut_->get_dict());
|
||
res_->copy_ap_of(aut_);
|
||
const unsigned num_scc = si_.scc_count();
|
||
std::vector<unsigned>* orig_aut =
|
||
aut_->get_named_prop<std::vector<unsigned>>("original-states");
|
||
std::vector<unsigned> orig_st;
|
||
if (orig_aut)
|
||
{
|
||
orig_st = std::move(*orig_aut);
|
||
std::const_pointer_cast<twa_graph>(aut_)
|
||
->set_named_prop("original-states", nullptr);
|
||
}
|
||
auto sccs = si_.split_aut();
|
||
for (unsigned scc = 0; scc < num_scc; ++scc)
|
||
{
|
||
auto sub_automaton = sccs[scc];
|
||
process_scc(sub_automaton);
|
||
}
|
||
|
||
link_sccs();
|
||
// During the execution, to_parity works on its own
|
||
// original-states and we must combine it with the property original
|
||
// states of aut_ to propagate the information.
|
||
if (orig_aut)
|
||
for (unsigned i = 0; i < orig_->size(); ++i)
|
||
(*orig_)[i] = orig_st[(*orig_)[i]];
|
||
res_->set_named_prop("original-states", orig_);
|
||
if (opt_.pretty_print)
|
||
res_->set_named_prop("state-names", names_);
|
||
if (res_->num_states() == 0)
|
||
add_res_state<memory_type>(NONE, {0, 0, {}});
|
||
res_->set_init_state(state_to_res_[aut_->get_init_state_number()]);
|
||
// There is only a subset of algorithm that can create an unreachable
|
||
// state
|
||
if (need_purge_)
|
||
res_->purge_unreachable_states();
|
||
// A special case is an automaton without edge. It implies
|
||
// max_color_used_ has not value so we need to test it.
|
||
if (max_color_used_ == 0)
|
||
{
|
||
assert(aut_->num_edges() == 0);
|
||
res_->set_acceptance(acc_cond(acc_cond::acc_code::f()));
|
||
}
|
||
else
|
||
{
|
||
res_->set_acceptance(acc_cond(acc_cond::acc_code::
|
||
parity(true, is_odd_,
|
||
max_color_used_)));
|
||
}
|
||
if (opt_.datas)
|
||
{
|
||
constexpr std::array<algorithm, 14>
|
||
algos = {BUCHI_TYPE, CAR, CO_BUCHI_TYPE, GENERIC_EMPTINESS, IAR_RABIN,
|
||
IAR_STREETT, NONE, PARITY_EQUIV, PARITY_PREFIX,
|
||
PARITY_PREFIX_GENERAL, PARITY_TYPE, RABIN_TO_BUCHI,
|
||
STREETT_TO_COBUCHI, TAR};
|
||
for (auto al : algos)
|
||
if (algo_used_ & al)
|
||
opt_.datas->algorithms_used.emplace_back(algorithm_to_str(al));
|
||
}
|
||
return res_;
|
||
}
|
||
|
||
to_parity_generator(const const_twa_graph_ptr &aut,
|
||
const to_parity_options opt)
|
||
: aut_(aut),
|
||
opt_(opt),
|
||
si_(aut),
|
||
state_to_res_(aut->num_states(), -1U)
|
||
{
|
||
auto aut_num = aut->num_states();
|
||
res_to_aut_.reserve(aut_num);
|
||
orig_ = new std::vector<unsigned>();
|
||
orig_->reserve(aut_num);
|
||
if (opt.pretty_print)
|
||
{
|
||
names_ = new std::vector<std::string>();
|
||
names_->reserve(aut_num);
|
||
}
|
||
}
|
||
};
|
||
|
||
twa_graph_ptr
|
||
to_parity(const const_twa_graph_ptr &aut,
|
||
const to_parity_options options)
|
||
{
|
||
bool is_max, is_odd;
|
||
if (aut->acc().is_parity(is_max, is_odd, false))
|
||
{
|
||
if (!is_max)
|
||
return change_parity(aut, parity_kind::parity_kind_max,
|
||
parity_style::parity_style_any);
|
||
else
|
||
{
|
||
auto res = make_twa_graph(aut, twa::prop_set::all());
|
||
res->copy_acceptance_of(aut);
|
||
return res;
|
||
}
|
||
}
|
||
to_parity_generator gen(aut, options);
|
||
return gen.run();
|
||
}
|
||
|
||
// Old version of CAR
|
||
|
||
namespace
|
||
{
|
||
struct lar_state
|
||
{
|
||
unsigned state;
|
||
std::vector<unsigned> perm;
|
||
|
||
bool operator<(const lar_state &s) const
|
||
{
|
||
return state == s.state ? perm < s.perm : state < s.state;
|
||
}
|
||
|
||
std::string to_string() const
|
||
{
|
||
std::ostringstream s;
|
||
s << state << " [";
|
||
unsigned ps = perm.size();
|
||
for (unsigned i = 0; i < ps; ++i)
|
||
{
|
||
if (i > 0)
|
||
s << ',';
|
||
s << perm[i];
|
||
}
|
||
s << ']';
|
||
return s.str();
|
||
}
|
||
};
|
||
|
||
class lar_generator
|
||
{
|
||
const const_twa_graph_ptr &aut_;
|
||
twa_graph_ptr res_;
|
||
const bool pretty_print;
|
||
|
||
std::map<lar_state, unsigned> lar2num;
|
||
|
||
public:
|
||
explicit lar_generator(const const_twa_graph_ptr &a, bool pretty_print)
|
||
: aut_(a), res_(nullptr), pretty_print(pretty_print)
|
||
{
|
||
}
|
||
|
||
twa_graph_ptr run()
|
||
{
|
||
res_ = make_twa_graph(aut_->get_dict());
|
||
res_->copy_ap_of(aut_);
|
||
|
||
std::deque<lar_state> todo;
|
||
auto get_state = [this, &todo](const lar_state &s)
|
||
{
|
||
auto it = lar2num.emplace(s, -1U);
|
||
if (it.second) // insertion took place
|
||
{
|
||
unsigned nb = res_->new_state();
|
||
it.first->second = nb;
|
||
todo.push_back(s);
|
||
}
|
||
return it.first->second;
|
||
};
|
||
|
||
std::vector<unsigned> initial_perm(aut_->num_sets());
|
||
std::iota(initial_perm.begin(), initial_perm.end(), 0);
|
||
{
|
||
lar_state s0{aut_->get_init_state_number(), initial_perm};
|
||
res_->set_init_state(get_state(s0));
|
||
}
|
||
|
||
scc_info si(aut_, scc_info_options::NONE);
|
||
// main loop
|
||
while (!todo.empty())
|
||
{
|
||
lar_state current = todo.front();
|
||
todo.pop_front();
|
||
|
||
// TODO: todo could store this number to avoid one lookup
|
||
unsigned src_num = get_state(current);
|
||
|
||
unsigned source_scc = si.scc_of(current.state);
|
||
for (const auto &e : aut_->out(current.state))
|
||
{
|
||
// find the new permutation
|
||
std::vector<unsigned> new_perm = current.perm;
|
||
unsigned h = 0;
|
||
for (unsigned k : e.acc.sets())
|
||
{
|
||
auto it = std::find(new_perm.begin(), new_perm.end(), k);
|
||
h = std::max(h, unsigned(new_perm.end() - it));
|
||
std::rotate(it, it + 1, new_perm.end());
|
||
}
|
||
|
||
if (source_scc != si.scc_of(e.dst))
|
||
{
|
||
new_perm = initial_perm;
|
||
h = 0;
|
||
}
|
||
|
||
lar_state dst{e.dst, new_perm};
|
||
unsigned dst_num = get_state(dst);
|
||
|
||
// Do the h last elements satisfy the acceptance condition?
|
||
// If they do, emit 2h, if they don't emit 2h+1.
|
||
acc_cond::mark_t m(new_perm.end() - h, new_perm.end());
|
||
bool rej = !aut_->acc().accepting(m);
|
||
res_->new_edge(src_num, dst_num, e.cond, {2 * h + rej});
|
||
}
|
||
}
|
||
|
||
// parity max even
|
||
unsigned sets = 2 * aut_->num_sets() + 2;
|
||
res_->set_acceptance(sets, acc_cond::acc_code::parity_max_even(sets));
|
||
|
||
if (pretty_print)
|
||
{
|
||
auto names = new std::vector<std::string>(res_->num_states());
|
||
for (const auto &p : lar2num)
|
||
(*names)[p.second] = p.first.to_string();
|
||
res_->set_named_prop("state-names", names);
|
||
}
|
||
|
||
return res_;
|
||
}
|
||
};
|
||
}
|
||
|
||
twa_graph_ptr
|
||
to_parity_old(const const_twa_graph_ptr &aut, bool pretty_print)
|
||
{
|
||
if (!aut->is_existential())
|
||
throw std::runtime_error("LAR does not handle alternation");
|
||
// if aut is already parity return it as is
|
||
if (aut->acc().is_parity())
|
||
return std::const_pointer_cast<twa_graph>(aut);
|
||
|
||
lar_generator gen(aut, pretty_print);
|
||
return gen.run();
|
||
}
|
||
|
||
// Old version of IAR
|
||
|
||
namespace
|
||
{
|
||
using perm_t = std::vector<unsigned>;
|
||
struct iar_state
|
||
{
|
||
unsigned state;
|
||
perm_t perm;
|
||
|
||
bool
|
||
operator<(const iar_state &other) const
|
||
{
|
||
return state == other.state ? perm < other.perm : state < other.state;
|
||
}
|
||
};
|
||
|
||
template <bool is_rabin>
|
||
class iar_generator
|
||
{
|
||
// helper functions: access fin and inf parts of the pairs
|
||
// these functions negate the Streett condition to see it as a Rabin one
|
||
const acc_cond::mark_t &
|
||
fin(unsigned k) const
|
||
{
|
||
if (is_rabin)
|
||
return pairs_[k].fin;
|
||
else
|
||
return pairs_[k].inf;
|
||
}
|
||
acc_cond::mark_t
|
||
inf(unsigned k) const
|
||
{
|
||
if (is_rabin)
|
||
return pairs_[k].inf;
|
||
else
|
||
return pairs_[k].fin;
|
||
}
|
||
|
||
public:
|
||
explicit iar_generator(const const_twa_graph_ptr &a,
|
||
const std::vector<acc_cond::rs_pair> &p,
|
||
const bool pretty_print)
|
||
: aut_(a), pairs_(p), scc_(scc_info(a)), pretty_print_(pretty_print),
|
||
state2pos_iar_states(aut_->num_states(), -1U)
|
||
{
|
||
}
|
||
|
||
twa_graph_ptr
|
||
run()
|
||
{
|
||
res_ = make_twa_graph(aut_->get_dict());
|
||
res_->copy_ap_of(aut_);
|
||
|
||
build_iar_scc(scc_.initial());
|
||
|
||
{
|
||
// resulting automaton has acceptance condition: parity max odd
|
||
// for Rabin-like input and parity max even for Streett-like input.
|
||
// with priorities ranging from 0 to 2*(nb pairs)
|
||
// /!\ priorities are shifted by -1 compared to the original paper
|
||
unsigned sets = 2 * pairs_.size() + 1;
|
||
res_->set_acceptance(sets, acc_cond::acc_code::parity_max(is_rabin,
|
||
sets));
|
||
}
|
||
{
|
||
unsigned s = iar2num.at(state2iar.at(aut_->get_init_state_number()));
|
||
res_->set_init_state(s);
|
||
}
|
||
|
||
if (pretty_print_)
|
||
{
|
||
unsigned nstates = res_->num_states();
|
||
auto names = new std::vector<std::string>(nstates);
|
||
for (auto e : res_->edges())
|
||
{
|
||
unsigned s = e.src;
|
||
iar_state iar = num2iar[s];
|
||
std::ostringstream st;
|
||
st << iar.state << ' ';
|
||
if (iar.perm.empty())
|
||
st << '[';
|
||
char sep = '[';
|
||
for (unsigned h : iar.perm)
|
||
{
|
||
st << sep << h;
|
||
sep = ',';
|
||
}
|
||
st << ']';
|
||
(*names)[s] = st.str();
|
||
}
|
||
res_->set_named_prop("state-names", names);
|
||
}
|
||
|
||
// there could be quite a number of unreachable states, prune them
|
||
res_->purge_unreachable_states();
|
||
return res_;
|
||
}
|
||
|
||
void
|
||
build_iar_scc(unsigned scc_num)
|
||
{
|
||
// we are working on an SCC: the state we start from does not matter
|
||
unsigned init = scc_.one_state_of(scc_num);
|
||
|
||
std::deque<iar_state> todo;
|
||
auto get_state = [&](const iar_state &s)
|
||
{
|
||
auto it = iar2num.find(s);
|
||
if (it == iar2num.end())
|
||
{
|
||
unsigned nb = res_->new_state();
|
||
iar2num[s] = nb;
|
||
num2iar[nb] = s;
|
||
unsigned iar_pos = iar_states.size();
|
||
unsigned old_newest_pos = state2pos_iar_states[s.state];
|
||
state2pos_iar_states[s.state] = iar_pos;
|
||
iar_states.push_back({s, old_newest_pos});
|
||
todo.push_back(s);
|
||
return nb;
|
||
}
|
||
return it->second;
|
||
};
|
||
|
||
auto get_other_scc = [this](unsigned state)
|
||
{
|
||
auto it = state2iar.find(state);
|
||
// recursively build the destination SCC if we detect that it has
|
||
// not been already built.
|
||
if (it == state2iar.end())
|
||
build_iar_scc(scc_.scc_of(state));
|
||
return iar2num.at(state2iar.at(state));
|
||
};
|
||
|
||
if (scc_.is_trivial(scc_num))
|
||
{
|
||
iar_state iar_s{init, perm_t()};
|
||
state2iar[init] = iar_s;
|
||
unsigned src_num = get_state(iar_s);
|
||
// Do not forget to connect to subsequent SCCs
|
||
for (const auto &e : aut_->out(init))
|
||
res_->new_edge(src_num, get_other_scc(e.dst), e.cond);
|
||
return;
|
||
}
|
||
|
||
// determine the pairs that appear in the SCC
|
||
auto colors = scc_.acc_sets_of(scc_num);
|
||
std::set<unsigned> scc_pairs;
|
||
for (unsigned k = 0; k != pairs_.size(); ++k)
|
||
if (!inf(k) || (colors & (pairs_[k].fin | pairs_[k].inf)))
|
||
scc_pairs.insert(k);
|
||
|
||
perm_t p0;
|
||
for (unsigned k : scc_pairs)
|
||
p0.push_back(k);
|
||
|
||
iar_state s0{init, p0};
|
||
get_state(s0); // put s0 in todo
|
||
|
||
// the main loop
|
||
while (!todo.empty())
|
||
{
|
||
iar_state current = todo.front();
|
||
todo.pop_front();
|
||
|
||
unsigned src_num = get_state(current);
|
||
|
||
for (const auto &e : aut_->out(current.state))
|
||
{
|
||
// connect to the appropriate state
|
||
if (scc_.scc_of(e.dst) != scc_num)
|
||
res_->new_edge(src_num, get_other_scc(e.dst), e.cond);
|
||
else
|
||
{
|
||
// find the new permutation
|
||
perm_t new_perm = current.perm;
|
||
// Count pairs whose fin-part is seen on this transition
|
||
unsigned seen_nb = 0;
|
||
// consider the pairs for this SCC only
|
||
for (unsigned k : scc_pairs)
|
||
if (e.acc & fin(k))
|
||
{
|
||
++seen_nb;
|
||
auto it = std::find(new_perm.begin(),
|
||
new_perm.end(),
|
||
k);
|
||
// move the pair in front of the permutation
|
||
std::rotate(new_perm.begin(), it, it + 1);
|
||
}
|
||
|
||
iar_state dst;
|
||
unsigned dst_num = -1U;
|
||
|
||
// Optimization: when several indices are seen in the
|
||
// transition, they move at the front of new_perm in any
|
||
// order. Check whether there already exists an iar_state
|
||
// that matches this condition.
|
||
|
||
auto iar_pos = state2pos_iar_states[e.dst];
|
||
while (iar_pos != -1U)
|
||
{
|
||
iar_state &tmp = iar_states[iar_pos].first;
|
||
iar_pos = iar_states[iar_pos].second;
|
||
if (std::equal(new_perm.begin() + seen_nb,
|
||
new_perm.end(),
|
||
tmp.perm.begin() + seen_nb))
|
||
{
|
||
dst = tmp;
|
||
dst_num = iar2num[dst];
|
||
break;
|
||
}
|
||
}
|
||
// if such a state was not found, build it
|
||
if (dst_num == -1U)
|
||
{
|
||
dst = iar_state{e.dst, new_perm};
|
||
dst_num = get_state(dst);
|
||
}
|
||
|
||
// find the maximal index encountered by this transition
|
||
unsigned maxint = -1U;
|
||
for (int k = current.perm.size() - 1; k >= 0; --k)
|
||
{
|
||
unsigned pk = current.perm[k];
|
||
if (!inf(pk) ||
|
||
(e.acc & (pairs_[pk].fin | pairs_[pk].inf)))
|
||
{
|
||
maxint = k;
|
||
break;
|
||
}
|
||
}
|
||
|
||
acc_cond::mark_t acc{};
|
||
if (maxint == -1U)
|
||
acc.set(0);
|
||
else if (e.acc & fin(current.perm[maxint]))
|
||
assign_color(acc, 2 * maxint + 2);
|
||
else
|
||
assign_color(acc, 2 * maxint + 1);
|
||
|
||
res_->new_edge(src_num, dst_num, e.cond, acc);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Optimization: find the bottom SCC of the sub-automaton we have just
|
||
// built. To that end, we have to ignore edges going out of scc_num.
|
||
auto leaving_edge = [&](unsigned d) constexpr
|
||
{
|
||
return scc_.scc_of(num2iar.at(d).state) != scc_num;
|
||
};
|
||
auto filter_edge = [](const twa_graph::edge_storage_t &,
|
||
unsigned dst,
|
||
void *filter_data) constexpr
|
||
{
|
||
decltype(leaving_edge) *data =
|
||
static_cast<decltype(leaving_edge) *>(filter_data);
|
||
|
||
if ((*data)(dst))
|
||
return scc_info::edge_filter_choice::ignore;
|
||
return scc_info::edge_filter_choice::keep;
|
||
};
|
||
scc_info sub_scc(res_, get_state(s0), filter_edge, &leaving_edge);
|
||
// SCCs are numbered in reverse topological order, so the bottom SCC
|
||
// has index 0.
|
||
const unsigned bscc = 0;
|
||
assert(sub_scc.succ(0).empty());
|
||
assert(
|
||
[&]()
|
||
{
|
||
for (unsigned s = 1; s != sub_scc.scc_count(); ++s)
|
||
if (sub_scc.succ(s).empty())
|
||
return false;
|
||
return true;
|
||
}());
|
||
|
||
assert(sub_scc.states_of(bscc).size()
|
||
>= scc_.states_of(scc_num).size());
|
||
|
||
// update state2iar
|
||
for (unsigned scc_state : sub_scc.states_of(bscc))
|
||
{
|
||
iar_state &iar = num2iar.at(scc_state);
|
||
if (state2iar.find(iar.state) == state2iar.end())
|
||
state2iar[iar.state] = iar;
|
||
}
|
||
}
|
||
|
||
private:
|
||
const const_twa_graph_ptr &aut_;
|
||
const std::vector<acc_cond::rs_pair> &pairs_;
|
||
const scc_info scc_;
|
||
twa_graph_ptr res_;
|
||
bool pretty_print_;
|
||
|
||
// to be used when entering a new SCC
|
||
// maps a state of aut_ onto an iar_state with the appropriate perm
|
||
std::map<unsigned, iar_state> state2iar;
|
||
|
||
std::map<iar_state, unsigned> iar2num;
|
||
std::map<unsigned, iar_state> num2iar;
|
||
|
||
std::vector<unsigned> state2pos_iar_states;
|
||
std::vector<std::pair<iar_state, unsigned>> iar_states;
|
||
};
|
||
|
||
// Make this a function different from iar_maybe(), so that
|
||
// iar() does not have to call a deprecated function.
|
||
static twa_graph_ptr
|
||
iar_maybe_(const const_twa_graph_ptr &aut, bool pretty_print)
|
||
{
|
||
std::vector<acc_cond::rs_pair> pairs;
|
||
if (!aut->acc().is_rabin_like(pairs))
|
||
if (!aut->acc().is_streett_like(pairs))
|
||
return nullptr;
|
||
else
|
||
{
|
||
iar_generator<false> gen(aut, pairs, pretty_print);
|
||
return gen.run();
|
||
}
|
||
else
|
||
{
|
||
iar_generator<true> gen(aut, pairs, pretty_print);
|
||
return gen.run();
|
||
}
|
||
}
|
||
}
|
||
|
||
twa_graph_ptr
|
||
iar(const const_twa_graph_ptr &aut, bool pretty_print)
|
||
{
|
||
if (auto res = iar_maybe_(aut, pretty_print))
|
||
return res;
|
||
throw std::runtime_error("iar() expects Rabin-like or Streett-like input");
|
||
}
|
||
|
||
twa_graph_ptr
|
||
iar_maybe(const const_twa_graph_ptr &aut, bool pretty_print)
|
||
{
|
||
return iar_maybe_(aut, pretty_print);
|
||
}
|
||
}
|