set_printer = nullptr) const;
/// \brief Construct an acc_code from a string.
///
/// The string should either follow the following grammar:
///
///
/// acc ::= "t"
/// | "f"
/// | "Inf" "(" num ")"
/// | "Fin" "(" num ")"
/// | "(" acc ")"
/// | acc "&" acc
/// | acc "|" acc
///
///
/// Where num is an integer and "&" has priority over "|". Note that
/// "Fin(!x)" and "Inf(!x)" are not supported by this parser.
///
/// Or the string could be the name of an acceptance condition, as
/// speficied in the HOA format. (E.g. "Rabin 2", "parity max odd 3",
/// "generalized-Rabin 4 2 1", etc.).
///
/// A spot::parse_error is thrown on syntax error.
acc_code(const char* input);
/// \brief Build an empty acceptance formula.
///
/// This is the same as t().
acc_code()
{
}
/// \brief Copy a part of another acceptance formula
acc_code(const acc_word* other)
: std::vector(other - other->sub.size, other + 1)
{
}
/// \brief prints the acceptance formula as text
SPOT_API
friend std::ostream& operator<<(std::ostream& os, const acc_code& code);
};
/// \brief Build an acceptance condition
///
/// This takes a number of sets \a n_sets, and an acceptance
/// formula (\a code) over those sets.
///
/// The number of sets should be at least cover all the sets used
/// in \a code.
acc_cond(unsigned n_sets = 0, const acc_code& code = {})
: num_(0U), all_({}), code_(code)
{
add_sets(n_sets);
uses_fin_acceptance_ = check_fin_acceptance();
}
/// \brief Build an acceptance condition
///
/// In this version, the number of sets is set the the smallest
/// number necessary for \a code.
acc_cond(const acc_code& code)
: num_(0U), all_({}), code_(code)
{
add_sets(code.used_sets().max_set());
uses_fin_acceptance_ = check_fin_acceptance();
}
/// \brief Copy an acceptance condition
acc_cond(const acc_cond& o)
: num_(o.num_), all_(o.all_), code_(o.code_),
uses_fin_acceptance_(o.uses_fin_acceptance_)
{
}
/// \brief Copy an acceptance condition
acc_cond& operator=(const acc_cond& o)
{
num_ = o.num_;
all_ = o.all_;
code_ = o.code_;
uses_fin_acceptance_ = o.uses_fin_acceptance_;
return *this;
}
~acc_cond()
{
}
/// \brief Change the acceptance formula.
///
/// Beware, this does not change the number of declared sets.
void set_acceptance(const acc_code& code)
{
code_ = code;
uses_fin_acceptance_ = check_fin_acceptance();
}
/// \brief Retrieve the acceptance formula
const acc_code& get_acceptance() const
{
return code_;
}
/// \brief Retrieve the acceptance formula
acc_code& get_acceptance()
{
return code_;
}
bool operator==(const acc_cond& other) const
{
return other.num_sets() == num_ && other.get_acceptance() == code_;
}
bool operator!=(const acc_cond& other) const
{
return !(*this == other);
}
/// \brief Whether the acceptance condition uses Fin terms
bool uses_fin_acceptance() const
{
return uses_fin_acceptance_;
}
/// \brief Whether the acceptance formula is "t" (true)
bool is_t() const
{
return code_.is_t();
}
/// \brief Whether the acceptance condition is "all"
///
/// In the HOA format, the acceptance condition "all" correspond
/// to the formula "t" with 0 declared acceptance sets.
bool is_all() const
{
return num_ == 0 && is_t();
}
/// \brief Whether the acceptance formula is "f" (false)
bool is_f() const
{
return code_.is_f();
}
/// \brief Whether the acceptance condition is "none"
///
/// In the HOA format, the acceptance condition "all" correspond
/// to the formula "f" with 0 declared acceptance sets.
bool is_none() const
{
return num_ == 0 && is_f();
}
/// \brief Whether the acceptance condition is "Büchi"
///
/// The acceptance condition is Büchi if its formula is Inf(0) and
/// only 1 set is used.
bool is_buchi() const
{
unsigned s = code_.size();
return num_ == 1 &&
s == 2 && code_[1].sub.op == acc_op::Inf && code_[0].mark == all_sets();
}
/// \brief Whether the acceptance condition is "co-Büchi"
///
/// The acceptance condition is co-Büchi if its formula is Fin(0) and
/// only 1 set is used.
bool is_co_buchi() const
{
return num_ == 1 && is_generalized_co_buchi();
}
/// \brief Change the acceptance condition to generalized-Büchi,
/// over all declared sets.
void set_generalized_buchi()
{
set_acceptance(inf(all_sets()));
}
/// \brief Change the acceptance condition to
/// generalized-co-Büchi, over all declared sets.
void set_generalized_co_buchi()
{
set_acceptance(fin(all_sets()));
}
/// \brief Whether the acceptance condition is "generalized-Büchi"
///
/// The acceptance condition with n sets is generalized-Büchi if its
/// formula is Inf(0)&Inf(1)&...&Inf(n-1).
bool is_generalized_buchi() const
{
unsigned s = code_.size();
return (s == 0 && num_ == 0) || (s == 2 && code_[1].sub.op == acc_op::Inf
&& code_[0].mark == all_sets());
}
/// \brief Whether the acceptance condition is "generalized-co-Büchi"
///
/// The acceptance condition with n sets is generalized-co-Büchi if its
/// formula is Fin(0)|Fin(1)|...|Fin(n-1).
bool is_generalized_co_buchi() const
{
unsigned s = code_.size();
return (s == 2 &&
code_[1].sub.op == acc_op::Fin && code_[0].mark == all_sets());
}
/// \brief Check if the acceptance condition matches the Rabin
/// acceptance of the HOA format
///
/// Rabin acceptance over 2n sets look like
/// `(Fin(0)&Inf(1))|...|(Fin(2n-2)&Inf(2n-1))`; i.e., a
/// disjunction of n pairs of the form `Fin(2i)&Inf(2i+1)`.
///
/// `f` is a special kind of Rabin acceptance with 0 pairs.
///
/// This function returns a number of pairs (>=0) or -1 if the
/// acceptance condition is not Rabin.
int is_rabin() const;
/// \brief Check if the acceptance condition matches the Streett
/// acceptance of the HOA format
///
/// Streett acceptance over 2n sets look like
/// `(Fin(0)|Inf(1))&...&(Fin(2n-2)|Inf(2n-1))`; i.e., a
/// conjunction of n pairs of the form `Fin(2i)|Inf(2i+1)`.
///
/// `t` is a special kind of Streett acceptance with 0 pairs.
///
/// This function returns a number of pairs (>=0) or -1 if the
/// acceptance condition is not Streett.
int is_streett() const;
/// \brief Rabin/streett pairs used by is_rabin_like and is_streett_like.
///
/// These pairs hold two marks which can each contain one or no set number.
///
/// For instance is_streett_like() rewrites Inf(0)&(Inf(2)|Fin(1))&Fin(3)
/// as three pairs: [(fin={},inf={0}),(fin={1},inf={2}),(fin={3},inf={})].
///
/// Empty marks should be interpreted in a way that makes them
/// false in Streett, and true in Rabin.
struct SPOT_API rs_pair
{
#ifndef SWIG
rs_pair() = default;
rs_pair(const rs_pair&) = default;
#endif
rs_pair(acc_cond::mark_t fin, acc_cond::mark_t inf) noexcept:
fin(fin),
inf(inf)
{}
acc_cond::mark_t fin;
acc_cond::mark_t inf;
bool operator==(rs_pair o) const
{
return fin == o.fin && inf == o.inf;
}
bool operator!=(rs_pair o) const
{
return fin != o.fin || inf != o.inf;
}
bool operator<(rs_pair o) const
{
return fin < o.fin || (!(o.fin < fin) && inf < o.inf);
}
bool operator<=(rs_pair o) const
{
return !(o < *this);
}
bool operator>(rs_pair o) const
{
return o < *this;
}
bool operator>=(rs_pair o) const
{
return !(*this < o);
}
};
/// \brief Test whether an acceptance condition is Streett-like
/// and returns each Streett pair in an std::vector.
///
/// An acceptance condition is Streett-like if it can be transformed into
/// a Streett acceptance with little modification to its automaton.
/// A Streett-like acceptance condition follows one of those rules:
/// -It is a conjunction of disjunctive clauses containing at most one
/// Inf and at most one Fin.
/// -It is true (with 0 pair)
/// -It is false (1 pair [0U, 0U])
bool is_streett_like(std::vector& pairs) const;
/// \brief Test whether an acceptance condition is Rabin-like
/// and returns each Rabin pair in an std::vector.
///
/// An acceptance condition is Rabin-like if it can be transformed into
/// a Rabin acceptance with little modification to its automaton.
/// A Rabin-like acceptance condition follows one of those rules:
/// -It is a disjunction of conjunctive clauses containing at most one
/// Inf and at most one Fin.
/// -It is true (1 pair [0U, 0U])
/// -It is false (0 pairs)
bool is_rabin_like(std::vector& pairs) const;
/// \brief Is the acceptance condition generalized-Rabin?
///
/// Check if the condition follows the generalized-Rabin
/// definition of the HOA format. So one Fin should be in front
/// of each generalized pair, and set numbers should all be used
/// once.
///
/// When true is returned, the \a pairs vector contains the number
/// of `Inf` term in each pair. Otherwise, \a pairs is emptied.
bool is_generalized_rabin(std::vector& pairs) const;
/// \brief Is the acceptance condition generalized-Streett?
///
/// There is no definition of generalized Streett in HOA v1,
/// so this uses the definition from the development version
/// of the HOA format, that should eventually become HOA v1.1 or
/// HOA v2.
///
/// One Inf should be in front of each generalized pair, and
/// set numbers should all be used once.
///
/// When true is returned, the \a pairs vector contains the number
/// of `Fin` term in each pair. Otherwise, \a pairs is emptied.
bool is_generalized_streett(std::vector& pairs) const;
/// \brief check is the acceptance condition matches one of the
/// four type of parity acceptance defined in the HOA format.
///
/// On success, this return true and sets \a max, and \a odd to
/// the type of parity acceptance detected. By default \a equiv =
/// false, and the parity acceptance should match exactly the
/// order of operators given in the HOA format. If \a equiv is
/// set, any formula that this logically equivalent to one of the
/// HOA format will be accepted.
bool is_parity(bool& max, bool& odd, bool equiv = false) const;
/// \brief check is the acceptance condition matches one of the
/// four type of parity acceptance defined in the HOA format.
bool is_parity() const
{
bool max;
bool odd;
return is_parity(max, odd);
}
// Return (true, m) if there exist some acceptance mark m that
// does not satisfy the acceptance condition. Return (false, 0U)
// otherwise.
std::pair unsat_mark() const
{
return sat_unsat_mark(false);
}
// Return (true, m) if there exist some acceptance mark m that
// does satisfy the acceptance condition. Return (false, 0U)
// otherwise.
std::pair sat_mark() const
{
return sat_unsat_mark(true);
}
protected:
bool check_fin_acceptance() const;
std::pair sat_unsat_mark(bool) const;
public:
/// \brief Construct a generalized Büchi acceptance
///
/// For the input `m={1,8,9}`, this constructs
/// `Inf(1)&Inf(8)&Inf(9)`.
///
/// Internally, such a formula is stored using a single word
/// `Inf({1,8,9})`.
/// @{
static acc_code inf(mark_t mark)
{
return acc_code::inf(mark);
}
static acc_code inf(std::initializer_list vals)
{
return inf(mark_t(vals.begin(), vals.end()));
}
/// @}
/// \brief Construct a generalized Büchi acceptance for
/// complemented sets.
///
/// For the input `m={1,8,9}`, this constructs
/// `Inf(!1)&Inf(!8)&Inf(!9)`.
///
/// Internally, such a formula is stored using a single word
/// `InfNeg({1,8,9})`.
///
/// Note that `InfNeg` formulas are not supported by most methods
/// of this class, and not supported by algorithms in Spot.
/// This is mostly used in the parser for HOA files: if the
/// input file uses `Inf(!0)` as acceptance condition, the
/// condition will eventually be rewritten as `Inf(0)` by toggling
/// the membership to set 0 of each transition.
/// @{
static acc_code inf_neg(mark_t mark)
{
return acc_code::inf_neg(mark);
}
static acc_code inf_neg(std::initializer_list vals)
{
return inf_neg(mark_t(vals.begin(), vals.end()));
}
/// @}
/// \brief Construct a generalized co-Büchi acceptance
///
/// For the input m={1,8,9}, this constructs Fin(1)|Fin(8)|Fin(9).
///
/// Internally, such a formula is stored using a single word
/// Fin({1,8,9}).
/// @{
static acc_code fin(mark_t mark)
{
return acc_code::fin(mark);
}
static acc_code fin(std::initializer_list vals)
{
return fin(mark_t(vals.begin(), vals.end()));
}
/// @}
/// \brief Construct a generalized co-Büchi acceptance for
/// complemented sets.
///
/// For the input `m={1,8,9}`, this constructs
/// `Fin(!1)|Fin(!8)|Fin(!9)`.
///
/// Internally, such a formula is stored using a single word
/// `FinNeg({1,8,9})`.
///
/// Note that `FinNeg` formulas are not supported by most methods
/// of this class, and not supported by algorithms in Spot.
/// This is mostly used in the parser for HOA files: if the
/// input file uses `Fin(!0)` as acceptance condition, the
/// condition will eventually be rewritten as `Fin(0)` by toggling
/// the membership to set 0 of each transition.
/// @{
static acc_code fin_neg(mark_t mark)
{
return acc_code::fin_neg(mark);
}
static acc_code fin_neg(std::initializer_list vals)
{
return fin_neg(mark_t(vals.begin(), vals.end()));
}
/// @}
/// \brief Add more sets to the acceptance condition.
///
/// This simply augment the number of sets, without changing the
/// acceptance formula.
unsigned add_sets(unsigned num)
{
if (num == 0)
return -1U;
unsigned j = num_;
num += j;
if (num > mark_t::max_accsets())
report_too_many_sets();
// Make sure we do not update if we raised an exception.
num_ = num;
all_ = all_sets_();
return j;
}
/// \brief Add a single set to the acceptance condition.
///
/// This simply augment the number of sets, without changing the
/// acceptance formula.
unsigned add_set()
{
return add_sets(1);
}
/// \brief Build a mark_t with a single set
mark_t mark(unsigned u) const
{
SPOT_ASSERT(u < num_sets());
return mark_t({u});
}
/// \brief Complement a mark_t
///
/// Complementation is done with respect to the number of sets
/// declared.
mark_t comp(const mark_t& l) const
{
return all_ ^ l;
}
/// \brief Construct a mark_t with all declared sets.
mark_t all_sets() const
{
return all_;
}
/// \brief Check whether visiting *exactly* all sets \a inf
/// infinitely often satisfies the acceptance condition.
bool accepting(mark_t inf) const
{
return code_.accepting(inf);
}
/// \brief Assuming that we will visit at least all sets in \a
/// inf, is there any chance that we will satisfy the condition?
///
/// This return false only when it is sure that visiting more
/// set will never make the condition satisfiable.
bool inf_satisfiable(mark_t inf) const
{
return code_.inf_satisfiable(inf);
}
/// \brief Check potential acceptance of an SCC.
///
/// Assuming that an SCC intersects all sets in \a
/// infinitely_often (i.e., for each set in \a infinitely_often,
/// there exist one marked transition in the SCC), and is
/// included in all sets in \a always_present (i.e., all
/// transitions are marked with \a always_present), this returns
/// one tree possible results:
/// - trival::yes() the SCC is necessarily accepting,
/// - trival::no() the SCC is necessarily rejecting,
/// - trival::maybe() the SCC could contain an accepting cycle.
trival maybe_accepting(mark_t infinitely_often, mark_t always_present) const
{
return code_.maybe_accepting(infinitely_often, always_present);
}
/// \brief Return an accepting subset of \a inf
///
/// This function works only on Fin-less acceptance, and returns a
/// subset of \a inf that is enough to satisfy the acceptance
/// condition. This is typically used when an accepting SCC that
/// visits all sets in \a inf has been found, and we want to find
/// an accepting cycle: maybe it is not necessary for the accepting
/// cycle to intersect all sets in \a inf.
///
/// This returns `mark_t({})` if \a inf does not satisfies the
/// acceptance condition, or if the acceptance condition is `t`.
/// So usually you should only use this method in cases you know
/// that the condition is satisfied.
mark_t accepting_sets(mark_t inf) const;
// FIXME: deprecate?
std::ostream& format(std::ostream& os, mark_t m) const
{
auto a = m; // ???
if (!a)
return os;
return os << m;
}
// FIXME: deprecate?
std::string format(mark_t m) const
{
std::ostringstream os;
format(os, m);
return os.str();
}
/// \brief The number of sets used in the acceptance condition.
unsigned num_sets() const
{
return num_;
}
/// \brief Compute useless acceptance sets given a list of mark_t
/// that occur in an SCC.
///
/// Assuming that the condition is generalized Büchi using all
/// declared sets, this scans all the mark_t between \a begin and
/// \a end, and return the set of acceptance sets that are useless
/// because they are always implied by other sets.
template
mark_t useless(iterator begin, iterator end) const
{
mark_t u = {}; // The set of useless sets
for (unsigned x = 0; x < num_; ++x)
{
// Skip sets that are already known to be useless.
if (u.has(x))
continue;
auto all = comp(u | mark_t({x}));
// Iterate over all mark_t, and keep track of
// set numbers that always appear with x.
for (iterator y = begin; y != end; ++y)
{
const mark_t& v = *y;
if (v.has(x))
{
all &= v;
if (!all)
break;
}
}
u |= all;
}
return u;
}
/// \brief Remove all the acceptance sets in \a rem.
///
/// If \a missing is set, the acceptance sets are assumed to be
/// missing from the automaton, and the acceptance is updated to
/// reflect this. For instance `(Inf(1)&Inf(2))|Fin(3)` will
/// become `Fin(3)` if we remove `2` because it is missing from this
/// automaton. Indeed there is no way to fulfill `Inf(1)&Inf(2)`
/// in this case. So essentially `missing=true` causes Inf(rem) to
/// become `f`, and `Fin(rem)` to become `t`.
///
/// If \a missing is unset, `Inf(rem)` become `t` while
/// `Fin(rem)` become `f`. Removing `2` from
/// `(Inf(1)&Inf(2))|Fin(3)` would then give `Inf(1)|Fin(3)`.
acc_cond remove(mark_t rem, bool missing) const
{
return {num_sets(), code_.remove(rem, missing)};
}
/// \brief Remove acceptance sets, and shift set numbers
///
/// Same as remove, but also shift set numbers in the result so
/// that all used set numbers are continuous.
acc_cond strip(mark_t rem, bool missing) const
{
return
{ num_sets() - (all_sets() & rem).count(), code_.strip(rem, missing) };
}
/// \brief For all `x` in \a m, replaces `Fin(x)` by `false`.
acc_cond force_inf(mark_t m) const
{
return {num_sets(), code_.force_inf(m)};
}
/// \brief Restrict an acceptance condition to a subset of set
/// numbers that are occurring at some point.
acc_cond restrict_to(mark_t rem) const
{
return {num_sets(), code_.remove(all_sets() - rem, true)};
}
/// \brief Return the name of this acceptance condition, in the
/// specified format.
///
/// The empty string is returned if no name is known.
///
/// \a fmt should be a combination of the following letters. (0)
/// no parameters, (a) accentuated, (b) abbreviated, (d) style
/// used in dot output, (g) no generalized parameter, (l)
/// recognize Street-like and Rabin-like, (m) no main parameter,
/// (p) no parity parameter, (o) name unknown acceptance as
/// 'other', (s) shorthand for 'lo0'.
std::string name(const char* fmt = "alo") const;
/// \brief Find a `Fin(i)` that is a unit clause.
///
/// This return a mark_t `{i}` such that `Fin(i)` appears as a
/// unit clause in the acceptance condition. I.e., either the
/// condition is exactly `Fin(i)`, or the condition has the form
/// `...&Fin(i)&...`. If there is no such `Fin(i)`, an empty
/// mark_t is returned.
///
/// If multiple unit-Fin appear as unit-clauses, the set of
/// those will be returned. For instance applied to
/// `Fin(0)&Fin(1)&(Inf(2)|Fin(3))``, this will return `{0,1}`.
mark_t fin_unit() const
{
return code_.fin_unit();
}
/// \brief Return one acceptance set i that appear as `Fin(i)`
/// in the condition.
///
/// Return -1 if no such set exist.
int fin_one() const
{
return code_.fin_one();
}
protected:
mark_t all_sets_() const
{
return mark_t::all() >> (spot::acc_cond::mark_t::max_accsets() - num_);
}
unsigned num_;
mark_t all_;
acc_code code_;
bool uses_fin_acceptance_ = false;
};
struct rs_pairs_view {
typedef std::vector rs_pairs;
// Creates view of pairs 'p' with restriction only to marks in 'm'
explicit rs_pairs_view(const rs_pairs& p, const acc_cond::mark_t& m)
: pairs_(p), view_marks_(m) {}
// Creates view of pairs without restriction to marks
explicit rs_pairs_view(const rs_pairs& p)
: rs_pairs_view(p, acc_cond::mark_t::all()) {}
acc_cond::mark_t infs() const
{
return do_view([&](const acc_cond::rs_pair& p)
{
return visible(p.inf) ? p.inf : acc_cond::mark_t({});
});
}
acc_cond::mark_t fins() const
{
return do_view([&](const acc_cond::rs_pair& p)
{
return visible(p.fin) ? p.fin : acc_cond::mark_t({});
});
}
acc_cond::mark_t fins_alone() const
{
return do_view([&](const acc_cond::rs_pair& p)
{
return !visible(p.inf) && visible(p.fin) ? p.fin
: acc_cond::mark_t({});
});
}
acc_cond::mark_t infs_alone() const
{
return do_view([&](const acc_cond::rs_pair& p)
{
return !visible(p.fin) && visible(p.inf) ? p.inf
: acc_cond::mark_t({});
});
}
acc_cond::mark_t paired_with_fin(unsigned mark) const
{
acc_cond::mark_t res = {};
for (const auto& p: pairs_)
if (p.fin.has(mark) && visible(p.fin) && visible(p.inf))
res |= p.inf;
return res;
}
const rs_pairs& pairs() const
{
return pairs_;
}
private:
template
acc_cond::mark_t do_view(const filter& filt) const
{
acc_cond::mark_t res = {};
for (const auto& p: pairs_)
res |= filt(p);
return res;
}
bool visible(const acc_cond::mark_t& v) const
{
return !!(view_marks_ & v);
}
const rs_pairs& pairs_;
acc_cond::mark_t view_marks_;
};
SPOT_API
std::ostream& operator<<(std::ostream& os, const acc_cond& acc);
/// @}
namespace internal
{
class SPOT_API mark_iterator
{
public:
typedef unsigned value_type;
typedef const value_type& reference;
typedef const value_type* pointer;
typedef std::ptrdiff_t difference_type;
typedef std::forward_iterator_tag iterator_category;
mark_iterator() noexcept
: m_({})
{
}
mark_iterator(acc_cond::mark_t m) noexcept
: m_(m)
{
}
bool operator==(mark_iterator m) const
{
return m_ == m.m_;
}
bool operator!=(mark_iterator m) const
{
return m_ != m.m_;
}
value_type operator*() const
{
SPOT_ASSERT(m_);
return m_.min_set() - 1;
}
mark_iterator& operator++()
{
m_.clear(this->operator*());
return *this;
}
mark_iterator operator++(int)
{
mark_iterator it = *this;
++(*this);
return it;
}
private:
acc_cond::mark_t m_;
};
class SPOT_API mark_container
{
public:
mark_container(spot::acc_cond::mark_t m) noexcept
: m_(m)
{
}
mark_iterator begin() const
{
return {m_};
}
mark_iterator end() const
{
return {};
}
private:
spot::acc_cond::mark_t m_;
};
}
inline spot::internal::mark_container acc_cond::mark_t::sets() const
{
return {*this};
}
}
namespace std
{
template<>
struct hash
{
size_t operator()(spot::acc_cond::mark_t m) const noexcept
{
return m.hash();
}
};
}