// -*- coding: utf-8 -*-
// Copyright (C) 2014-2018 Laboratoire de Recherche et Développement
// de l'Epita (LRDE).
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
#pragma once
#include
#include
namespace spot
{
class scc_info;
namespace internal
{
struct keep_all
{
template
bool operator()(Iterator, Iterator) const noexcept
{
return true;
}
};
// Keep only transitions that have at least one destination in the
// current SCC.
struct keep_inner_scc
{
private:
const std::vector& sccof_;
unsigned desired_scc_;
public:
keep_inner_scc(const std::vector& sccof, unsigned desired_scc)
: sccof_(sccof), desired_scc_(desired_scc)
{
}
template
bool operator()(Iterator begin, Iterator end) const noexcept
{
bool want = false;
while (begin != end)
if (sccof_[*begin++] == desired_scc_)
{
want = true;
break;
}
return want;
}
};
template
class SPOT_API scc_edge_iterator
{
public:
typedef typename std::conditional::value,
const typename Graph::edge_storage_t,
typename Graph::edge_storage_t>::type
value_type;
typedef value_type& reference;
typedef value_type* pointer;
typedef std::ptrdiff_t difference_type;
typedef std::forward_iterator_tag iterator_category;
typedef std::vector::const_iterator state_iterator;
typedef typename std::conditional::value,
const typename Graph::edge_vector_t,
typename Graph::edge_vector_t>::type
tv_t;
typedef typename std::conditional::value,
const typename Graph::state_vector,
typename Graph::state_vector>::type
sv_t;
typedef const typename Graph::dests_vector_t dv_t;
protected:
state_iterator pos_;
state_iterator end_;
unsigned t_;
tv_t* tv_;
sv_t* sv_;
dv_t* dv_;
Filter filt_;
void inc_state_maybe_()
{
while (!t_ && (++pos_ != end_))
t_ = (*sv_)[*pos_].succ;
}
void inc_()
{
t_ = (*tv_)[t_].next_succ;
inc_state_maybe_();
}
bool ignore_current()
{
unsigned dst = (*this)->dst;
if ((int)dst >= 0)
// Non-universal branching => a single destination.
return !filt_(&(*this)->dst, 1 + &(*this)->dst);
// Universal branching => multiple destinations.
const unsigned* d = dv_->data() + ~dst;
return !filt_(d + 1, d + *d + 1);
}
public:
scc_edge_iterator(state_iterator begin, state_iterator end,
tv_t* tv, sv_t* sv, dv_t* dv, Filter filt) noexcept
: pos_(begin), end_(end), t_(0), tv_(tv), sv_(sv), dv_(dv), filt_(filt)
{
if (pos_ == end_)
return;
t_ = (*sv_)[*pos_].succ;
inc_state_maybe_();
while (pos_ != end_ && ignore_current())
inc_();
}
scc_edge_iterator& operator++()
{
do
inc_();
while (pos_ != end_ && ignore_current());
return *this;
}
scc_edge_iterator operator++(int)
{
scc_edge_iterator old = *this;
++*this;
return old;
}
bool operator==(scc_edge_iterator o) const
{
return pos_ == o.pos_ && t_ == o.t_;
}
bool operator!=(scc_edge_iterator o) const
{
return pos_ != o.pos_ || t_ != o.t_;
}
reference operator*() const
{
return (*tv_)[t_];
}
pointer operator->() const
{
return &**this;
}
};
template
class SPOT_API scc_edges
{
public:
typedef scc_edge_iterator iter_t;
typedef typename iter_t::tv_t tv_t;
typedef typename iter_t::sv_t sv_t;
typedef typename iter_t::dv_t dv_t;
typedef typename iter_t::state_iterator state_iterator;
private:
state_iterator begin_;
state_iterator end_;
tv_t* tv_;
sv_t* sv_;
dv_t* dv_;
Filter filt_;
public:
scc_edges(state_iterator begin, state_iterator end,
tv_t* tv, sv_t* sv, dv_t* dv, Filter filt) noexcept
: begin_(begin), end_(end), tv_(tv), sv_(sv), dv_(dv), filt_(filt)
{
}
iter_t begin() const
{
return {begin_, end_, tv_, sv_, dv_, filt_};
}
iter_t end() const
{
return {end_, end_, nullptr, nullptr, nullptr, filt_};
}
};
}
/// \ingroup twa_misc
/// \brief Storage for SCC related information.
class SPOT_API scc_info_node
{
public:
typedef std::vector scc_succs;
friend class scc_info;
protected:
scc_succs succ_;
std::vector states_; // States of the component
unsigned one_state_;
acc_cond::mark_t acc_;
acc_cond::mark_t common_;
bool trivial_:1;
bool accepting_:1; // Necessarily accepting
bool rejecting_:1; // Necessarily rejecting
bool useful_:1;
public:
scc_info_node() noexcept:
acc_({}), trivial_(true), accepting_(false),
rejecting_(false), useful_(false)
{
}
scc_info_node(acc_cond::mark_t acc,
acc_cond::mark_t common, bool trivial) noexcept
: acc_(acc), common_(common),
trivial_(trivial), accepting_(false),
rejecting_(false), useful_(false)
{
}
bool is_trivial() const
{
return trivial_;
}
/// \brief True if we know that the SCC has an accepting cycle
///
/// Note that both is_accepting() and is_rejecting() may return
/// false if an SCC interesects a mix of Fin and Inf sets.
/// Call determine_unknown_acceptance() to decide.
bool is_accepting() const
{
return accepting_;
}
/// \brief True if we know that all cycles in the SCC are rejecting
///
/// Note that both is_accepting() and is_rejecting() may return
/// false if an SCC interesects a mix of Fin and Inf sets.
/// Call determine_unknown_acceptance() to decide.
bool is_rejecting() const
{
return rejecting_;
}
bool is_useful() const
{
return useful_;
}
acc_cond::mark_t acc_marks() const
{
return acc_;
}
acc_cond::mark_t common_marks() const
{
return common_;
}
const std::vector& states() const
{
return states_;
}
unsigned one_state() const
{
return one_state_;
}
const scc_succs& succ() const
{
return succ_;
}
};
/// \ingroup twa_misc
/// \brief Options to alter the behavior of scc_info
enum class scc_info_options
{
/// Stop exploring when an accepting SCC is found, and do not track
/// the states of each SCC.
NONE = 0,
/// Stop exploring after an accepting SCC has been found.
/// Using this option forbids future uses of is_useful_scc() and
/// is_useful_state(). Using it will also cause the output of
/// succ() to be incomplete.
STOP_ON_ACC = 1,
/// Keep a vector of all states belonging to each SCC. Using this
/// option is a precondition for using states_of(), edges_of(),
/// inner_edges_of(), states_on_acc_cycle_of(), and
/// determine_unknown_acceptance().
TRACK_STATES = 2,
/// Keep a list of successors of each SCCs.
/// Using this option is a precondition for using succ(),
/// is_useful_scc(), and is_useful_state().
TRACK_SUCCS = 4,
/// Conditionally track states if the acceptance conditions uses Fin.
/// This is sufficiant for determine_unknown_acceptance().
TRACK_STATES_IF_FIN_USED = 8,
/// Default behavior: explore everything and track states and succs.
ALL = TRACK_STATES | TRACK_SUCCS,
};
inline
bool operator!(scc_info_options me)
{
return me == scc_info_options::NONE;
}
inline
scc_info_options operator&(scc_info_options left, scc_info_options right)
{
typedef std::underlying_type_t ut;
return static_cast(static_cast(left)
& static_cast(right));
}
inline
scc_info_options operator|(scc_info_options left, scc_info_options right)
{
typedef std::underlying_type_t ut;
return static_cast(static_cast(left)
| static_cast(right));
}
/// \ingroup twa_misc
/// \brief Compute an SCC map and gather assorted information.
///
/// This takes twa_graph as input and compute its SCCs. This
/// class maps all input states to their SCCs, and vice-versa.
/// It allows iterating over all SCCs of the automaton, and checks
/// their acceptance or non-acceptance.
///
/// SCC are numbered in reverse topological order, i.e. the SCC of the
/// initial state has the highest number, and if s1 is reachable from
/// s2, then s1 < s2. Many algorithms depend on this property to
/// determine in what order to iterate the SCCs.
///
/// Additionally this class can be used on alternating automata, but
/// in this case, universal transitions are handled like existential
/// transitions. It still makes sense to check which states belong
/// to the same SCC, but the acceptance information computed by
/// this class is meaningless.
class SPOT_API scc_info
{
public:
// scc_node used to be an inner class, but Swig 3.0.10 does not
// support that yet.
typedef scc_info_node scc_node;
typedef scc_info_node::scc_succs scc_succs;
/// @{
/// \brief An edge_filter may be called on each edge to decide what
/// to do with it.
///
/// The edge filter is called with an edge and a destination. (In
/// existential automata the destination is already given by the
/// edge, but in alternating automata, one edge may have several
/// destinations, and in this case the filter will be called for
/// each destination.) The filter should return a value from
/// edge_filter_choice.
///
/// \c keep means to use the edge normally, as if no filter had
/// been given. \c ignore means to pretend the edge does not
/// exist (if the destination is only reachable through this edge,
/// it will not be visited). \c cut also ignores the edge, but
/// it remembers to visit the destination state (as if it were an
/// initial state) in case it is not reachable otherwise.
///
/// Note that successors between SCCs can only be maintained for
/// edges that are kept. If some edges are ignored or cut, the
/// SCC graph that you can explore with scc_info::initial() and
/// scc_info::succ() will be restricted to the portion reachable
/// with "keep" edges. Additionally SCCs might be created when
/// edges are cut, but those will not be reachable from
/// scc_info::initial()..
enum class edge_filter_choice { keep, ignore, cut };
typedef edge_filter_choice
(*edge_filter)(const twa_graph::edge_storage_t& e, unsigned dst,
void* filter_data);
/// @}
protected:
std::vector sccof_;
std::vector node_;
const_twa_graph_ptr aut_;
unsigned initial_state_;
edge_filter filter_;
void* filter_data_;
int one_acc_scc_ = -1;
scc_info_options options_;
// Update the useful_ bits. Called automatically.
void determine_usefulness();
const scc_node& node(unsigned scc) const
{
return node_[scc];
}
#ifndef SWIG
private:
[[noreturn]] static void report_need_track_states();
[[noreturn]] static void report_need_track_succs();
[[noreturn]] static void report_incompatible_stop_on_acc();
#endif
public:
/// @{
/// \brief Create the scc_info map for \a aut
scc_info(const_twa_graph_ptr aut,
// Use ~0U instead of -1U to work around a bug in Swig.
// See https://github.com/swig/swig/issues/993
unsigned initial_state = ~0U,
edge_filter filter = nullptr,
void* filter_data = nullptr,
scc_info_options options = scc_info_options::ALL);
scc_info(const_twa_graph_ptr aut, scc_info_options options)
: scc_info(aut, ~0U, nullptr, nullptr, options)
{
}
/// @}
const_twa_graph_ptr get_aut() const
{
return aut_;
}
unsigned scc_count() const
{
return node_.size();
}
/// Return the number of one accepting SCC if any, -1 otherwise.
int one_accepting_scc() const
{
return one_acc_scc_;
}
bool reachable_state(unsigned st) const
{
return scc_of(st) != -1U;
}
unsigned scc_of(unsigned st) const
{
return sccof_[st];
}
std::vector::const_iterator begin() const
{
return node_.begin();
}
std::vector::const_iterator end() const
{
return node_.end();
}
std::vector::const_iterator cbegin() const
{
return node_.cbegin();
}
std::vector::const_iterator cend() const
{
return node_.cend();
}
std::vector::const_reverse_iterator rbegin() const
{
return node_.rbegin();
}
std::vector::const_reverse_iterator rend() const
{
return node_.rend();
}
const std::vector& states_of(unsigned scc) const
{
if (SPOT_UNLIKELY(!(options_ & scc_info_options::TRACK_STATES)))
report_need_track_states();
return node(scc).states();
}
/// \brief A fake container to iterate over all edges leaving any
/// state of an SCC.
///
/// The difference with inner_edges_of() is that edges_of() include
/// outgoing edges from all the states, even if they leave the SCC.
internal::scc_edges
edges_of(unsigned scc) const
{
auto& states = states_of(scc);
return {states.begin(), states.end(),
&aut_->edge_vector(), &aut_->states(),
&aut_->get_graph().dests_vector(),
internal::keep_all()};
}
/// \brief A fake container to iterate over all edges between
/// states of an SCC.
///
/// The difference with edges_of() is that inner_edges_of()
/// ignores edges leaving the SCC. In the case of an
/// alternating automaton, an edge is considered to be part of the
/// SCC of one of its destination is in the SCC.
internal::scc_edges
inner_edges_of(unsigned scc) const
{
auto& states = states_of(scc);
return {states.begin(), states.end(),
&aut_->edge_vector(), &aut_->states(),
&aut_->get_graph().dests_vector(),
internal::keep_inner_scc(sccof_, scc)};
}
unsigned one_state_of(unsigned scc) const
{
return node(scc).one_state();
}
/// \brief Get number of the SCC containing the initial state.
unsigned initial() const
{
SPOT_ASSERT(filter_ || scc_count() - 1 == scc_of(initial_state_));
return scc_of(initial_state_);
}
const scc_succs& succ(unsigned scc) const
{
if (SPOT_UNLIKELY(!(options_ & scc_info_options::TRACK_SUCCS)))
report_need_track_succs();
return node(scc).succ();
}
bool is_trivial(unsigned scc) const
{
return node(scc).is_trivial();
}
SPOT_DEPRECATED("use acc_sets_of() instead")
acc_cond::mark_t acc(unsigned scc) const
{
return acc_sets_of(scc);
}
bool is_accepting_scc(unsigned scc) const
{
return node(scc).is_accepting();
}
bool is_rejecting_scc(unsigned scc) const
{
return node(scc).is_rejecting();
}
/// \brief Study the SCCs that are currently reported neither as
/// accepting nor as rejecting because of the presence of Fin sets
///
/// This simply calls check_scc_emptiness() on undeterminate SCCs.
void determine_unknown_acceptance();
/// \brief Recompute whether an SCC is accepting or not.
///
/// This is an internal function of
/// determine_unknown_acceptance().
bool check_scc_emptiness(unsigned n);
bool is_useful_scc(unsigned scc) const
{
if (SPOT_UNLIKELY(!!(options_ & scc_info_options::STOP_ON_ACC)))
report_incompatible_stop_on_acc();
if (SPOT_UNLIKELY(!(options_ & scc_info_options::TRACK_SUCCS)))
report_need_track_succs();
return node(scc).is_useful();
}
bool is_useful_state(unsigned st) const
{
return reachable_state(st) && is_useful_scc(scc_of(st));
}
/// \brief Returns, for each accepting SCC, the set of all marks appearing
/// in it.
std::vector> marks() const;
std::set marks_of(unsigned scc) const;
// Same as above, with old names.
SPOT_DEPRECATED("use marks() instead")
std::vector> used_acc() const
{
return marks();
}
SPOT_DEPRECATED("use marks_of() instead")
std::set used_acc_of(unsigned scc) const
{
return marks_of(scc);
}
/// \brief Returns, for a given SCC, the set of all colors appearing in it.
/// It is the set of colors that appear in some mark among those returned by
/// marks_of().
acc_cond::mark_t acc_sets_of(unsigned scc) const
{
return node(scc).acc_marks();
}
/// Returns, for a given SCC, the set of colors that appear on all of its
/// transitions.
acc_cond::mark_t common_sets_of(unsigned scc) const
{
return node(scc).common_marks();
}
std::vector weak_sccs() const;
bdd scc_ap_support(unsigned scc) const;
/// \brief Split an SCC into multiple automata separated by some
/// acceptance sets.
///
/// Pretend that the transitions of SCC \a scc that belong to any
/// of the sets given in \a sets have been removed, and return a
/// set of automata necessary to cover all remaining states.
///
/// Set \a preserve_names to True if you want to keep the original
/// name of each states for display. (This is a bit slower.)
std::vector split_on_sets(unsigned scc,
acc_cond::mark_t sets,
bool preserve_names = false) const;
protected:
/// \brief: Recursive function used by states_on_acc_cycle_of().
void
states_on_acc_cycle_of_rec(unsigned scc,
acc_cond::mark_t all_fin,
acc_cond::mark_t all_inf,
unsigned nb_pairs,
std::vector& pairs,
std::vector& res,
std::vector& old) const;
public:
/// \brief: Get all states visited by any accepting cycles of the 'scc'.
///
/// Throws an exception if the automaton does not have a 'Streett-like'
/// acceptance condition.
std::vector
states_on_acc_cycle_of(unsigned scc) const;
};
/// \brief Dump the SCC graph of \a aut on \a out.
///
/// If \a sccinfo is not given, it will be computed.
SPOT_API std::ostream&
dump_scc_info_dot(std::ostream& out,
const_twa_graph_ptr aut, scc_info* sccinfo = nullptr);
}