spot  1.99.4a
graph.hh
1 // -*- coding: utf-8 -*-
2 // Copyright (C) 2014, 2015 Laboratoire de Recherche et Développement
3 // de l'Epita.
4 //
5 // This file is part of Spot, a model checking library.
6 //
7 // Spot is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 3 of the License, or
10 // (at your option) any later version.
11 //
12 // Spot is distributed in the hope that it will be useful, but WITHOUT
13 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
15 // License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 
20 #pragma once
21 
22 #include "misc/common.hh"
23 #include <vector>
24 #include <type_traits>
25 #include <tuple>
26 #include <cassert>
27 #include <iterator>
28 #include <algorithm>
29 #include <iostream>
30 #include <type_traits>
31 
32 namespace spot
33 {
34  template <typename State_Data, typename Edge_Data, bool Alternating = false>
35  class SPOT_API digraph;
36 
37  namespace internal
38  {
39  template <typename Of, typename ...Args>
41  {
42  static const bool value = false;
43  };
44 
45  template <typename Of, typename Arg1, typename ...Args>
46  struct first_is_base_of<Of, Arg1, Args...>
47  {
48  static const bool value =
49  std::is_base_of<Of, typename std::decay<Arg1>::type>::value;
50  };
51 
52 
53  // The boxed_label class stores Data as an attribute called
54  // "label" if boxed is true. It is an empty class if Data is
55  // void, and it simply inherits from Data if boxed is false.
56  //
57  // The data() method offers an homogeneous access to the Data
58  // instance.
59  template <typename Data, bool boxed = !std::is_class<Data>::value>
60  struct SPOT_API boxed_label
61  {
62  typedef Data data_t;
63  Data label;
64 
65  template <typename... Args,
66  typename = typename std::enable_if<
67  !first_is_base_of<boxed_label, Args...>::value>::type>
68  boxed_label(Args&&... args)
69  noexcept(std::is_nothrow_constructible<Data, Args...>::value)
70  : label{std::forward<Args>(args)...}
71  {
72  }
73 
74  // if Data is a POD type, G++ 4.8.2 wants default values for all
75  // label fields unless we define this default constructor here.
76  explicit boxed_label()
77  noexcept(std::is_nothrow_constructible<Data>::value)
78  {
79  }
80 
81  Data& data()
82  {
83  return label;
84  }
85 
86  const Data& data() const
87  {
88  return label;
89  }
90 
91  bool operator<(const boxed_label& other) const
92  {
93  return label < other.label;
94  }
95  };
96 
97  template <>
98  struct SPOT_API boxed_label<void, true>: public std::tuple<>
99  {
100  typedef std::tuple<> data_t;
101  std::tuple<>& data()
102  {
103  return *this;
104  }
105 
106  const std::tuple<>& data() const
107  {
108  return *this;
109  }
110 
111  };
112 
113  template <typename Data>
114  struct SPOT_API boxed_label<Data, false>: public Data
115  {
116  typedef Data data_t;
117 
118  template <typename... Args,
119  typename = typename std::enable_if<
120  !first_is_base_of<boxed_label, Args...>::value>::type>
121  boxed_label(Args&&... args)
122  noexcept(std::is_nothrow_constructible<Data, Args...>::value)
123  : Data{std::forward<Args>(args)...}
124  {
125  }
126 
127  // if Data is a POD type, G++ 4.8.2 wants default values for all
128  // label fields unless we define this default constructor here.
129  explicit boxed_label()
130  noexcept(std::is_nothrow_constructible<Data>::value)
131  {
132  }
133 
134  Data& data()
135  {
136  return *this;
137  }
138 
139  const Data& data() const
140  {
141  return *this;
142  }
143  };
144 
146  // State storage for digraphs
148 
149  // We have two implementations, one with attached State_Data, and
150  // one without.
151 
152  template <typename Edge, typename State_Data>
153  struct SPOT_API distate_storage final: public State_Data
154  {
155  Edge succ = 0; // First outgoing edge (used when iterating)
156  Edge succ_tail = 0; // Last outgoing edge (used for
157  // appending new edges)
158 
159  template <typename... Args,
160  typename = typename std::enable_if<
161  !first_is_base_of<distate_storage, Args...>::value>::type>
162  distate_storage(Args&&... args)
163  noexcept(std::is_nothrow_constructible<State_Data, Args...>::value)
164  : State_Data{std::forward<Args>(args)...}
165  {
166  }
167  };
168 
170  // Edge storage
172 
173  // Again two implementation: one with label, and one without.
174 
175  template <typename StateIn,
176  typename StateOut, typename Edge, typename Edge_Data>
177  struct SPOT_API edge_storage final: public Edge_Data
178  {
179  typedef Edge edge;
180 
181  StateOut dst; // destination
182  Edge next_succ; // next outgoing edge with same
183  // source, or 0
184  StateIn src; // source
185 
186  explicit edge_storage()
187  noexcept(std::is_nothrow_constructible<Edge_Data>::value)
188  : Edge_Data{}
189  {
190  }
191 
192  template <typename... Args>
193  edge_storage(StateOut dst, Edge next_succ,
194  StateIn src, Args&&... args)
195  noexcept(std::is_nothrow_constructible<Edge_Data, Args...>::value
196  && std::is_nothrow_constructible<StateOut, StateOut>::value
197  && std::is_nothrow_constructible<Edge, Edge>::value)
198  : Edge_Data{std::forward<Args>(args)...},
199  dst(dst), next_succ(next_succ), src(src)
200  {
201  }
202 
203  bool operator<(const edge_storage& other) const
204  {
205  if (src < other.src)
206  return true;
207  if (src > other.src)
208  return false;
209  // This might be costly if the destination is a vector
210  if (dst < other.dst)
211  return true;
212  if (dst > other.dst)
213  return false;
214  return this->data() < other.data();
215  }
216 
217  bool operator==(const edge_storage& other) const
218  {
219  return src == other.src &&
220  dst == other.dst &&
221  this->data() == other.data();
222  }
223  };
224 
226  // Edge iterator
228 
229  // This holds a graph and a edge number that is the start of
230  // a list, and it iterates over all the edge_storage_t elements
231  // of that list.
232 
233  template <typename Graph>
234  class SPOT_API edge_iterator:
235  std::iterator<std::forward_iterator_tag,
236  typename
237  std::conditional<std::is_const<Graph>::value,
238  const typename Graph::edge_storage_t,
239  typename Graph::edge_storage_t>::type>
240  {
241  typedef
242  std::iterator<std::forward_iterator_tag,
243  typename
244  std::conditional<std::is_const<Graph>::value,
245  const typename Graph::edge_storage_t,
246  typename Graph::edge_storage_t>::type>
247  super;
248  public:
249  typedef typename Graph::edge edge;
250 
251  edge_iterator() noexcept
252  : g_(nullptr), t_(0)
253  {
254  }
255 
256  edge_iterator(Graph* g, edge t) noexcept
257  : g_(g), t_(t)
258  {
259  }
260 
261  bool operator==(edge_iterator o) const
262  {
263  return t_ == o.t_;
264  }
265 
266  bool operator!=(edge_iterator o) const
267  {
268  return t_ != o.t_;
269  }
270 
271  typename super::reference
272  operator*()
273  {
274  return g_->edge_storage(t_);
275  }
276 
277  typename super::pointer
278  operator->()
279  {
280  return &g_->edge_storage(t_);
281  }
282 
283  edge_iterator operator++()
284  {
285  t_ = operator*().next_succ;
286  return *this;
287  }
288 
289  edge_iterator operator++(int)
290  {
291  edge_iterator ti = *this;
292  t_ = operator*().next_succ;
293  return ti;
294  }
295 
296  operator bool() const
297  {
298  return t_;
299  }
300 
301  edge trans() const
302  {
303  return t_;
304  }
305 
306  protected:
307  Graph* g_;
308  edge t_;
309  };
310 
311  template <typename Graph>
312  class SPOT_API killer_edge_iterator: public edge_iterator<Graph>
313  {
314  typedef edge_iterator<Graph> super;
315  public:
316  typedef typename Graph::state_storage_t state_storage_t;
317  typedef typename Graph::edge edge;
318 
319  killer_edge_iterator(Graph* g, edge t, state_storage_t& src) noexcept
320  : super(g, t), src_(src), prev_(0)
321  {
322  }
323 
324  killer_edge_iterator operator++()
325  {
326  prev_ = this->t_;
327  this->t_ = this->operator*().next_succ;
328  return *this;
329  }
330 
331  killer_edge_iterator operator++(int)
332  {
333  killer_edge_iterator ti = *this;
334  ++*this;
335  return ti;
336  }
337 
338  // Erase the current edge and advance the iterator.
339  void erase()
340  {
341  edge next = this->operator*().next_succ;
342 
343  // Update source state and previous edges
344  if (prev_)
345  {
346  this->g_->edge_storage(prev_).next_succ = next;
347  }
348  else
349  {
350  if (src_.succ == this->t_)
351  src_.succ = next;
352  }
353  if (src_.succ_tail == this->t_)
354  {
355  src_.succ_tail = prev_;
356  assert(next == 0);
357  }
358 
359  // Erased edges have themselves as next_succ.
360  this->operator*().next_succ = this->t_;
361 
362  // Advance iterator to next edge.
363  this->t_ = next;
364 
365  ++this->g_->killed_edge_;
366  }
367 
368  protected:
369  state_storage_t& src_;
370  edge prev_;
371  };
372 
373 
375  // State OUT
377 
378  // Fake container listing the outgoing edges of a state.
379 
380  template <typename Graph>
381  class SPOT_API state_out
382  {
383  public:
384  typedef typename Graph::edge edge;
385  state_out(Graph* g, edge t) noexcept
386  : g_(g), t_(t)
387  {
388  }
389 
390  edge_iterator<Graph> begin()
391  {
392  return {g_, t_};
393  }
394 
396  {
397  return {};
398  }
399 
400  void recycle(edge t)
401  {
402  t_ = t;
403  }
404 
405  protected:
406  Graph* g_;
407  edge t_;
408  };
409 
411  // all_trans
413 
414  template <typename Graph>
415  class SPOT_API all_edge_iterator:
416  std::iterator<std::forward_iterator_tag,
417  typename
418  std::conditional<std::is_const<Graph>::value,
419  const typename Graph::edge_storage_t,
420  typename Graph::edge_storage_t>::type>
421  {
422  typedef
423  std::iterator<std::forward_iterator_tag,
424  typename
425  std::conditional<std::is_const<Graph>::value,
426  const typename Graph::edge_storage_t,
427  typename Graph::edge_storage_t>::type>
428  super;
429 
430  typedef typename std::conditional<std::is_const<Graph>::value,
431  const typename Graph::edge_vector_t,
432  typename Graph::edge_vector_t>::type
433  tv_t;
434 
435  unsigned t_;
436  tv_t& tv_;
437 
438  void skip_()
439  {
440  unsigned s = tv_.size();
441  do
442  ++t_;
443  while (t_ < s && tv_[t_].next_succ == t_);
444  }
445 
446  public:
447  all_edge_iterator(unsigned pos, tv_t& tv) noexcept
448  : t_(pos), tv_(tv)
449  {
450  skip_();
451  }
452 
453  all_edge_iterator(tv_t& tv) noexcept
454  : t_(tv.size()), tv_(tv)
455  {
456  }
457 
458  all_edge_iterator& operator++()
459  {
460  skip_();
461  return *this;
462  }
463 
464  all_edge_iterator operator++(int)
465  {
466  all_edge_iterator old = *this;
467  ++*this;
468  return old;
469  }
470 
471  bool operator==(all_edge_iterator o) const
472  {
473  return t_ == o.t_;
474  }
475 
476  bool operator!=(all_edge_iterator o) const
477  {
478  return t_ != o.t_;
479  }
480 
481  typename super::reference
482  operator*()
483  {
484  return tv_[t_];
485  }
486 
487  typename super::pointer
488  operator->()
489  {
490  return &tv_[t_];
491  }
492  };
493 
494 
495  template <typename Graph>
496  class SPOT_API all_trans
497  {
498  typedef typename std::conditional<std::is_const<Graph>::value,
499  const typename Graph::edge_vector_t,
500  typename Graph::edge_vector_t>::type
501  tv_t;
503  tv_t& tv_;
504  public:
505 
506  all_trans(tv_t& tv) noexcept
507  : tv_(tv)
508  {
509  }
510 
511  iter_t begin()
512  {
513  return {0, tv_};
514  }
515 
516  iter_t end()
517  {
518  return {tv_};
519  }
520  };
521 
522  }
523 
524 
525  // The actual graph implementation
526 
527  template <typename State_Data, typename Edge_Data, bool Alternating>
528  class digraph
529  {
530  friend class internal::edge_iterator<digraph>;
531  friend class internal::edge_iterator<const digraph>;
533 
534  public:
535  typedef internal::edge_iterator<digraph> iterator;
536  typedef internal::edge_iterator<const digraph> const_iterator;
537 
538  static constexpr bool alternating()
539  {
540  return Alternating;
541  }
542 
543  // Extra data to store on each state or edge.
544  typedef State_Data state_data_t;
545  typedef Edge_Data edge_data_t;
546 
547  // State and edges are identified by their indices in some
548  // vector.
549  typedef unsigned state;
550  typedef unsigned edge;
551 
552  // The type of an output state (when seen from a edge)
553  // depends on the kind of graph we build
554  typedef typename std::conditional<Alternating,
555  std::vector<state>,
556  state>::type out_state;
557 
558  typedef internal::distate_storage<edge,
559  internal::boxed_label<State_Data>>
560  state_storage_t;
561  typedef internal::edge_storage<state, out_state, edge,
562  internal::boxed_label<Edge_Data>>
563  edge_storage_t;
564  typedef std::vector<state_storage_t> state_vector;
565  typedef std::vector<edge_storage_t> edge_vector_t;
566  protected:
567  state_vector states_;
568  edge_vector_t edges_;
569  // Number of erased edges.
570  unsigned killed_edge_;
571  public:
578  digraph(unsigned max_states = 10, unsigned max_trans = 0)
579  : killed_edge_(0)
580  {
581  states_.reserve(max_states);
582  if (max_trans == 0)
583  max_trans = max_states * 2;
584  edges_.reserve(max_trans + 1);
585  // Edge number 0 is not used, because we use this index
586  // to mark the absence of a edge.
587  edges_.resize(1);
588  // This causes edge 0 to be considered as dead.
589  edges_[0].next_succ = 0;
590  }
591 
592  unsigned num_states() const
593  {
594  return states_.size();
595  }
596 
597  unsigned num_edges() const
598  {
599  return edges_.size() - killed_edge_ - 1;
600  }
601 
602  bool valid_trans(edge t) const
603  {
604  // Erased edges have their next_succ pointing to
605  // themselves.
606  return (t < edges_.size() &&
607  edges_[t].next_succ != t);
608  }
609 
610  template <typename... Args>
611  state new_state(Args&&... args)
612  {
613  state s = states_.size();
614  states_.emplace_back(std::forward<Args>(args)...);
615  return s;
616  }
617 
618  template <typename... Args>
619  state new_states(unsigned n, Args&&... args)
620  {
621  state s = states_.size();
622  states_.reserve(s + n);
623  while (n--)
624  states_.emplace_back(std::forward<Args>(args)...);
625  return s;
626  }
627 
628  state_storage_t&
629  state_storage(state s)
630  {
631  assert(s < states_.size());
632  return states_[s];
633  }
634 
635  const state_storage_t&
636  state_storage(state s) const
637  {
638  assert(s < states_.size());
639  return states_[s];
640  }
641 
642  // Do not use State_Data& as return type, because State_Data might
643  // be void.
644  typename state_storage_t::data_t&
645  state_data(state s)
646  {
647  assert(s < states_.size());
648  return states_[s].data();
649  }
650 
651  // May not be called on states with no data.
652  const typename state_storage_t::data_t&
653  state_data(state s) const
654  {
655  assert(s < states_.size());
656  return states_[s].data();
657  }
658 
659  edge_storage_t&
660  edge_storage(edge s)
661  {
662  assert(s < edges_.size());
663  return edges_[s];
664  }
665 
666  const edge_storage_t&
667  edge_storage(edge s) const
668  {
669  assert(s < edges_.size());
670  return edges_[s];
671  }
672 
673  typename edge_storage_t::data_t&
674  edge_data(edge s)
675  {
676  assert(s < edges_.size());
677  return edges_[s].data();
678  }
679 
680  const typename edge_storage_t::data_t&
681  edge_data(edge s) const
682  {
683  assert(s < edges_.size());
684  return edges_[s].data();
685  }
686 
687  template <typename... Args>
688  edge
689  new_edge(state src, out_state dst, Args&&... args)
690  {
691  assert(src < states_.size());
692 
693  edge t = edges_.size();
694  edges_.emplace_back(dst, 0, src, std::forward<Args>(args)...);
695 
696  edge st = states_[src].succ_tail;
697  assert(st < t || !st);
698  if (!st)
699  states_[src].succ = t;
700  else
701  edges_[st].next_succ = t;
702  states_[src].succ_tail = t;
703  return t;
704  }
705 
706  state index_of_state(const state_storage_t& ss) const
707  {
708  assert(!states_.empty());
709  return &ss - &states_.front();
710  }
711 
712  edge index_of_edge(const edge_storage_t& tt) const
713  {
714  assert(!edges_.empty());
715  return &tt - &edges_.front();
716  }
717 
718  internal::state_out<digraph>
719  out(state src)
720  {
721  return {this, states_[src].succ};
722  }
723 
724  internal::state_out<digraph>
725  out(state_storage_t& src)
726  {
727  return out(index_of_state(src));
728  }
729 
730  internal::state_out<const digraph>
731  out(state src) const
732  {
733  return {this, states_[src].succ};
734  }
735 
736  internal::state_out<const digraph>
737  out(state_storage_t& src) const
738  {
739  return out(index_of_state(src));
740  }
741 
742  internal::killer_edge_iterator<digraph>
743  out_iteraser(state_storage_t& src)
744  {
745  return {this, src.succ, src};
746  }
747 
748  internal::killer_edge_iterator<digraph>
749  out_iteraser(state src)
750  {
751  return out_iteraser(state_storage(src));
752  }
753 
754  const state_vector& states() const
755  {
756  return states_;
757  }
758 
759  state_vector& states()
760  {
761  return states_;
762  }
763 
764  internal::all_trans<const digraph> edges() const
765  {
766  return edges_;
767  }
768 
769  internal::all_trans<digraph> edges()
770  {
771  return edges_;
772  }
773 
774  // When using this method, beware that the first entry (edge
775  // #0) is not a real edge, and that any edge with
776  // next_succ pointing to itself is an erased edge.
777  //
778  // You should probably use edges() instead.
779  const edge_vector_t& edge_vector() const
780  {
781  return edges_;
782  }
783 
784  edge_vector_t& edge_vector()
785  {
786  return edges_;
787  }
788 
789  bool is_dead_edge(unsigned t) const
790  {
791  return edges_[t].next_succ == t;
792  }
793 
794  bool is_dead_edge(const edge_storage_t& t) const
795  {
796  return t.next_succ == index_of_edge(t);
797  }
798 
799 
800  // To help debugging
801  void dump_storage(std::ostream& o) const
802  {
803  unsigned tend = edges_.size();
804  for (unsigned t = 1; t < tend; ++t)
805  {
806  o << 't' << t << ": (s"
807  << edges_[t].src << ", s"
808  << edges_[t].dst << ") t"
809  << edges_[t].next_succ << '\n';
810  }
811  unsigned send = states_.size();
812  for (unsigned s = 0; s < send; ++s)
813  {
814  o << 's' << s << ": t"
815  << states_[s].succ << " t"
816  << states_[s].succ_tail << '\n';
817  }
818  }
819 
820  // Remove all dead edges. The edges_ vector is left
821  // in a state that is incorrect and should eventually be fixed by
822  // a call to chain_edges_() before any iteration on the
823  // successor of a state is performed.
824  void remove_dead_edges_()
825  {
826  if (killed_edge_ == 0)
827  return;
828  auto i = std::remove_if(edges_.begin() + 1, edges_.end(),
829  [this](const edge_storage_t& t) {
830  return this->is_dead_edge(t);
831  });
832  edges_.erase(i, edges_.end());
833  killed_edge_ = 0;
834  }
835 
836  // This will invalidate all iterators, and also destroy edge
837  // chains. Call chain_edges_() immediately afterwards
838  // unless you know what you are doing.
839  template<class Predicate = std::less<edge_storage_t>>
840  void sort_edges_(Predicate p = Predicate())
841  {
842  //std::cerr << "\nbefore\n";
843  //dump_storage(std::cerr);
844  std::stable_sort(edges_.begin() + 1, edges_.end(), p);
845  }
846 
847  // Should be called only when it is known that all edges
848  // with the same destination are consecutive in the vector.
849  void chain_edges_()
850  {
851  state last_src = -1U;
852  edge tend = edges_.size();
853  for (edge t = 1; t < tend; ++t)
854  {
855  state src = edges_[t].src;
856  if (src != last_src)
857  {
858  states_[src].succ = t;
859  if (last_src != -1U)
860  {
861  states_[last_src].succ_tail = t - 1;
862  edges_[t - 1].next_succ = 0;
863  }
864  while (++last_src != src)
865  {
866  states_[last_src].succ = 0;
867  states_[last_src].succ_tail = 0;
868  }
869  }
870  else
871  {
872  edges_[t - 1].next_succ = t;
873  }
874  }
875  if (last_src != -1U)
876  {
877  states_[last_src].succ_tail = tend - 1;
878  edges_[tend - 1].next_succ = 0;
879  }
880  unsigned send = states_.size();
881  while (++last_src != send)
882  {
883  states_[last_src].succ = 0;
884  states_[last_src].succ_tail = 0;
885  }
886  //std::cerr << "\nafter\n";
887  //dump_storage(std::cerr);
888  }
889 
890  // Rename all the states in the edge vector. The
891  // edges_ vector is left in a state that is incorrect and
892  // should eventually be fixed by a call to chain_edges_()
893  // before any iteration on the successor of a state is performed.
894  void rename_states_(const std::vector<unsigned>& newst)
895  {
896  assert(newst.size() == states_.size());
897  unsigned tend = edges_.size();
898  for (unsigned t = 1; t < tend; t++)
899  {
900  edges_[t].dst = newst[edges_[t].dst];
901  edges_[t].src = newst[edges_[t].src];
902  }
903  }
904 
905  void defrag_states(std::vector<unsigned>&& newst, unsigned used_states)
906  {
907  assert(newst.size() == states_.size());
908  assert(used_states > 0);
909 
910  //std::cerr << "\nbefore defrag\n";
911  //dump_storage(std::cerr);
912 
913  // Shift all states in states_, as indicated by newst.
914  unsigned send = states_.size();
915  for (state s = 0; s < send; ++s)
916  {
917  state dst = newst[s];
918  if (dst == s)
919  continue;
920  if (dst == -1U)
921  {
922  // This is an erased state. Mark all its edges as
923  // dead (i.e., t.next_succ should point to t for each of
924  // them).
925  auto t = states_[s].succ;
926  while (t)
927  std::swap(t, edges_[t].next_succ);
928  continue;
929  }
930  states_[dst] = std::move(states_[s]);
931  }
932  states_.resize(used_states);
933 
934  // Shift all edges in edges_. The algorithm is
935  // similar to remove_if, but it also keeps the correspondence
936  // between the old and new index as newidx[old] = new.
937  unsigned tend = edges_.size();
938  std::vector<edge> newidx(tend);
939  unsigned dest = 1;
940  for (edge t = 1; t < tend; ++t)
941  {
942  if (is_dead_edge(t))
943  continue;
944  if (t != dest)
945  edges_[dest] = std::move(edges_[t]);
946  newidx[t] = dest;
947  ++dest;
948  }
949  edges_.resize(dest);
950  killed_edge_ = 0;
951 
952  // Adjust next_succ and dst pointers in all edges.
953  for (edge t = 1; t < dest; ++t)
954  {
955  auto& tr = edges_[t];
956  tr.next_succ = newidx[tr.next_succ];
957  tr.dst = newst[tr.dst];
958  tr.src = newst[tr.src];
959  assert(tr.dst != -1U);
960  }
961 
962  // Adjust succ and succ_tails pointers in all states.
963  for (auto& s: states_)
964  {
965  s.succ = newidx[s.succ];
966  s.succ_tail = newidx[s.succ_tail];
967  }
968 
969  //std::cerr << "\nafter defrag\n";
970  //dump_storage(std::cerr);
971  }
972  };
973 }
Definition: graph.hh:32
Definition: graph.hh:60
digraph(unsigned max_states=10, unsigned max_trans=0)
construct an empty graph
Definition: graph.hh:578
Definition: graph.hh:35
Definition: graph.hh:234
Definition: graph.hh:177
Definition: graph.hh:153
Definition: graph.hh:496
Definition: graph.hh:40
Definition: graph.hh:415
Definition: graph.hh:381
Definition: graph.hh:312

Please direct any question, comment, or bug report to the Spot mailing list at spot@lrde.epita.fr.
Generated on Sun Oct 11 2015 10:50:08 for spot by doxygen 1.8.9.1