1 // -*- coding: utf-8 -*- 2 // Copyright (C) 2011, 2013-2019, 2021 Laboratoire de Recherche et 3 // Développement de l'Epita (LRDE). 4 // Copyright (C) 2004, 2005 Laboratoire d'Informatique de Paris 6 (LIP6), 5 // département Systèmes Répartis Coopératifs (SRC), Université Pierre 6 // et Marie Curie. 7 // 8 // This file is part of Spot, a model checking library. 9 // 10 // Spot is free software; you can redistribute it and/or modify it 11 // under the terms of the GNU General Public License as published by 12 // the Free Software Foundation; either version 3 of the License, or 13 // (at your option) any later version. 14 // 15 // Spot is distributed in the hope that it will be useful, but WITHOUT 16 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 17 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 18 // License for more details. 19 // 20 // You should have received a copy of the GNU General Public License 21 // along with this program. If not, see <http://www.gnu.org/licenses/>. 22 23 /// FIXME: 24 /// * Test some heuristics on the order of visit of the successors in the blue 25 /// dfs: 26 /// - favorize the arcs conducting to the blue stack (the states of color 27 /// cyan) 28 /// - in this category, favorize the labelled arcs 29 /// - for the remaining ones, favorize the arcs labelled by the greatest 30 /// number of new acceptance conditions (notice that this number may evolve 31 /// after the visit of previous successors). 32 /// 33 /// * Add a bit-state hashing version. 34 35 //#define TRACE 36 37 #include "config.h" 38 #include <iostream> 39 #ifdef TRACE 40 #define trace std::cerr 41 #else 42 #define trace while (0) std::cerr 43 #endif 44 45 #include <cassert> 46 #include <vector> 47 #include <stack> 48 #include <spot/misc/hash.hh> 49 #include <spot/twa/twa.hh> 50 #include <spot/twaalgos/emptiness.hh> 51 #include <spot/twaalgos/emptiness_stats.hh> 52 #include <spot/twaalgos/tau03opt.hh> 53 #include <spot/priv/weight.hh> 54 #include <spot/twaalgos/ndfs_result.hxx> 55 56 namespace spot 57 { 58 namespace 59 { 60 enum color {WHITE, CYAN, BLUE}; 61 62 /// \brief Emptiness checker on spot::tgba automata having a finite number 63 /// of acceptance conditions (i.e. a TGBA). 64 template <typename heap> 65 class tau03_opt_search : public emptiness_check, public ec_statistics 66 { 67 public: 68 /// \brief Initialize the search algorithm on the automaton \a a tau03_opt_search(const const_twa_ptr & a,size_t size,option_map o)69 tau03_opt_search(const const_twa_ptr& a, size_t size, option_map o) 70 : emptiness_check(a, o), 71 current_weight(a->acc()), 72 h(size), 73 use_condition_stack(o.get("condstack")), 74 use_ordering(use_condition_stack && o.get("ordering")), 75 use_weights(o.get("weights", 1)), 76 use_red_weights(use_weights && o.get("redweights", 1)) 77 { 78 if (a->acc().uses_fin_acceptance()) 79 throw std::runtime_error("tau03opt requires Fin-less acceptance"); 80 } 81 ~tau03_opt_search()82 virtual ~tau03_opt_search() 83 { 84 // Release all iterators on the stacks. 85 while (!st_blue.empty()) 86 { 87 h.pop_notify(st_blue.front().s); 88 a_->release_iter(st_blue.front().it); 89 st_blue.pop_front(); 90 } 91 while (!st_red.empty()) 92 { 93 h.pop_notify(st_red.front().s); 94 a_->release_iter(st_red.front().it); 95 st_red.pop_front(); 96 } 97 } 98 99 /// \brief Perform an emptiness check. 100 /// 101 /// \return non null pointer iff the algorithm has found an 102 /// accepting path. check()103 virtual emptiness_check_result_ptr check() override 104 { 105 if (!st_blue.empty()) 106 return nullptr; 107 assert(st_red.empty()); 108 const state* s0 = a_->get_init_state(); 109 inc_states(); 110 h.add_new_state(s0, CYAN, current_weight); 111 push(st_blue, s0, bddfalse, {}); 112 auto t = std::static_pointer_cast<tau03_opt_search> 113 (this->emptiness_check::shared_from_this()); 114 if (dfs_blue()) 115 return std::make_shared<ndfs_result<tau03_opt_search<heap>, heap>>(t); 116 return nullptr; 117 } 118 print_stats(std::ostream & os) const119 virtual std::ostream& print_stats(std::ostream &os) const override 120 { 121 os << states() << " distinct nodes visited" << std::endl; 122 os << transitions() << " transitions explored" << std::endl; 123 os << max_depth() << " nodes for the maximal stack depth" << std::endl; 124 return os; 125 } 126 get_heap() const127 const heap& get_heap() const 128 { 129 return h; 130 } 131 get_st_blue() const132 const stack_type& get_st_blue() const 133 { 134 return st_blue; 135 } 136 get_st_red() const137 const stack_type& get_st_red() const 138 { 139 return st_red; 140 } 141 142 private: push(stack_type & st,const state * s,const bdd & label,acc_cond::mark_t acc)143 void push(stack_type& st, const state* s, 144 const bdd& label, acc_cond::mark_t acc) 145 { 146 inc_depth(); 147 twa_succ_iterator* i = a_->succ_iter(s); 148 i->first(); 149 st.emplace_front(s, i, label, acc); 150 } 151 pop(stack_type & st)152 void pop(stack_type& st) 153 { 154 dec_depth(); 155 a_->release_iter(st.front().it); 156 st.pop_front(); 157 } 158 project_acc(acc_cond::mark_t acc) const159 acc_cond::mark_t project_acc(acc_cond::mark_t acc) const 160 { 161 if (!use_ordering) 162 return acc; 163 // FIXME: This should be improved. 164 std::vector<unsigned> res; 165 unsigned max = a_->num_sets(); 166 for (unsigned n = 0; n < max && acc.has(n); ++n) 167 res.emplace_back(n); 168 return acc_cond::mark_t(res.begin(), res.end()); 169 } 170 171 /// \brief weight of the state on top of the blue stack. 172 weight current_weight; 173 174 /// \brief Stack of the blue dfs. 175 stack_type st_blue; 176 177 /// \brief Stack of the red dfs. 178 stack_type st_red; 179 180 /// \brief Map where each visited state is colored 181 /// by the last dfs visiting it. 182 heap h; 183 184 /// Whether to use the "condition stack". 185 bool use_condition_stack; 186 /// Whether to use an ordering between the acceptance conditions. 187 /// Effective only if using the condition stack. 188 bool use_ordering; 189 /// Whether to use weights to abort earlier. 190 bool use_weights; 191 /// Whether to use weights in the red dfs. 192 bool use_red_weights; 193 dfs_blue()194 bool dfs_blue() 195 { 196 while (!st_blue.empty()) 197 { 198 stack_item& f = st_blue.front(); 199 trace << "DFS_BLUE treats: " << a_->format_state(f.s) << std::endl; 200 if (!f.it->done()) 201 { 202 const state *s_prime = f.it->dst(); 203 trace << " Visit the successor: " 204 << a_->format_state(s_prime) << std::endl; 205 bdd label = f.it->cond(); 206 auto acc = f.it->acc(); 207 // Go down the edge (f.s, <label, acc>, s_prime) 208 f.it->next(); 209 inc_transitions(); 210 typename heap::color_ref c_prime = h.get_color_ref(s_prime); 211 if (c_prime.is_white()) 212 { 213 trace << " It is white, go down" << std::endl; 214 if (use_weights) 215 current_weight.add(acc); 216 inc_states(); 217 h.add_new_state(s_prime, CYAN, current_weight); 218 push(st_blue, s_prime, label, acc); 219 } 220 else 221 { 222 typename heap::color_ref c = h.get_color_ref(f.s); 223 assert(!c.is_white()); 224 if (c_prime.get_color() == CYAN 225 && a_->acc().accepting 226 (current_weight.diff(a_->acc(), c_prime. get_weight()) 227 | c.get_acc() | acc | c_prime.get_acc())) 228 { 229 trace << " It is cyan and acceptance condition " 230 << "is reached, report cycle" << std::endl; 231 c_prime.cumulate_acc(a_->acc().all_sets()); 232 push(st_red, s_prime, label, acc); 233 return true; 234 } 235 else 236 { 237 trace << " It is cyan or blue and"; 238 auto acu = acc | c.get_acc(); 239 auto acp = project_acc(acu); 240 if ((c_prime.get_acc() & acp) != acp) 241 { 242 trace << " a propagation is needed, " 243 << "start a red dfs" << std::endl; 244 c_prime.cumulate_acc(acp); 245 push(st_red, s_prime, label, acc); 246 if (dfs_red(acu)) 247 return true; 248 } 249 else 250 { 251 trace << " no propagation is needed, pop it." 252 << std::endl; 253 h.pop_notify(s_prime); 254 } 255 } 256 } 257 } 258 else 259 // Backtrack the edge 260 // (predecessor of f.s in st_blue, <f.label, f.acc>, f.s) 261 { 262 trace << " All the successors have been visited" << std::endl; 263 stack_item f_dest(f); 264 pop(st_blue); 265 if (use_weights) 266 current_weight.sub(f_dest.acc); 267 typename heap::color_ref c_prime = h.get_color_ref(f_dest.s); 268 assert(!c_prime.is_white()); 269 c_prime.set_color(BLUE); 270 if (!st_blue.empty()) 271 { 272 typename heap::color_ref c = 273 h.get_color_ref(st_blue.front().s); 274 assert(!c.is_white()); 275 auto acu = f_dest.acc | c.get_acc(); 276 auto acp = project_acc(acu); 277 if ((c_prime.get_acc() & acp) != acp) 278 { 279 trace << " The arc from " 280 << a_->format_state(st_blue.front().s) 281 << " to the current state implies to " 282 << " start a red dfs" << std::endl; 283 c_prime.cumulate_acc(acp); 284 push(st_red, f_dest.s, f_dest.label, f_dest.acc); 285 if (dfs_red(acu)) 286 return true; 287 } 288 else 289 { 290 trace << " Pop it" << std::endl; 291 h.pop_notify(f_dest.s); 292 } 293 } 294 else 295 { 296 trace << " Pop it" << std::endl; 297 h.pop_notify(f_dest.s); 298 } 299 } 300 } 301 return false; 302 } 303 304 bool dfs_red(acc_cond::mark_t acu)305 dfs_red(acc_cond::mark_t acu) 306 { 307 assert(!st_red.empty()); 308 309 // These are useful only when USE_CONDITION_STACK is set. 310 typedef std::pair<acc_cond::mark_t, unsigned> cond_level; 311 std::stack<cond_level> condition_stack; 312 unsigned depth = 1; 313 condition_stack.emplace(acc_cond::mark_t({}), 0); 314 315 while (!st_red.empty()) 316 { 317 stack_item& f = st_red.front(); 318 trace << "DFS_RED treats: " << a_->format_state(f.s) << std::endl; 319 if (!f.it->done()) 320 { 321 const state *s_prime = f.it->dst(); 322 trace << " Visit the successor: " 323 << a_->format_state(s_prime) << std::endl; 324 bdd label = f.it->cond(); 325 auto acc = f.it->acc(); 326 // Go down the edge (f.s, <label, acc>, s_prime) 327 f.it->next(); 328 inc_transitions(); 329 typename heap::color_ref c_prime = h.get_color_ref(s_prime); 330 if (c_prime.is_white()) 331 { 332 trace << " It is white, pop it" << std::endl; 333 s_prime->destroy(); 334 continue; 335 } 336 else if (c_prime.get_color() == CYAN && 337 a_->acc().accepting 338 (acc | acu | c_prime.get_acc() | 339 (use_red_weights ? 340 current_weight.diff(a_->acc(), 341 c_prime. 342 get_weight()) 343 : acc_cond::mark_t({})))) 344 { 345 trace << " It is cyan and acceptance condition " 346 << "is reached, report cycle" << std::endl; 347 c_prime.cumulate_acc(a_->acc().all_sets()); 348 push(st_red, s_prime, label, acc); 349 return true; 350 } 351 acc_cond::mark_t acp; 352 if (use_ordering) 353 acp = project_acc(acu | acc | c_prime.get_acc()); 354 else if (use_condition_stack) 355 acp = acu | acc; 356 else 357 acp = acu; 358 if ((c_prime.get_acc() & acp) != acp) 359 { 360 trace << " It is cyan or blue and propagation " 361 << "is needed, go down" 362 << std::endl; 363 c_prime.cumulate_acc(acp); 364 push(st_red, s_prime, label, acc); 365 if (use_condition_stack) 366 { 367 auto old = acu; 368 acu |= acc; 369 condition_stack.emplace(acu - old, depth); 370 } 371 ++depth; 372 } 373 else 374 { 375 trace << " It is cyan or blue and no propagation " 376 << "is needed , pop it" << std::endl; 377 h.pop_notify(s_prime); 378 } 379 } 380 else // Backtrack 381 { 382 trace << " All the successors have been visited, pop it" 383 << std::endl; 384 h.pop_notify(f.s); 385 pop(st_red); 386 --depth; 387 if (condition_stack.top().second == depth) 388 { 389 acu -= condition_stack.top().first; 390 condition_stack.pop(); 391 } 392 } 393 } 394 assert(depth == 0); 395 assert(condition_stack.empty()); 396 return false; 397 } 398 399 }; 400 401 class explicit_tau03_opt_search_heap final 402 { 403 typedef state_map<std::pair<weight, acc_cond::mark_t>> hcyan_type; 404 typedef state_map<std::pair<color, acc_cond::mark_t>> hash_type; 405 public: 406 class color_ref final 407 { 408 public: color_ref(hash_type * h,hcyan_type * hc,const state * s,const weight * w,acc_cond::mark_t * a)409 color_ref(hash_type* h, hcyan_type* hc, const state* s, 410 const weight* w, acc_cond::mark_t* a) 411 : is_cyan(true), w(w), ph(h), phc(hc), ps(s), pc(nullptr), acc(a) 412 { 413 } color_ref(color * c,acc_cond::mark_t * a)414 color_ref(color* c, acc_cond::mark_t* a) 415 : is_cyan(false), pc(c), acc(a) 416 { 417 } get_color() const418 color get_color() const 419 { 420 if (is_cyan) 421 return CYAN; 422 return *pc; 423 } get_weight() const424 const weight& get_weight() const 425 { 426 assert(is_cyan); 427 return *w; 428 } set_color(color c)429 void set_color(color c) 430 { 431 assert(!is_white()); 432 if (is_cyan) 433 { 434 assert(c != CYAN); 435 std::pair<hash_type::iterator, bool> p; 436 p = ph->emplace(ps, std::make_pair(c, *acc)); 437 assert(p.second); 438 acc = &(p.first->second.second); 439 int i = phc->erase(ps); 440 assert(i == 1); 441 (void)i; 442 } 443 else 444 { 445 SPOT_ASSUME(pc); 446 *pc=c; 447 } 448 } get_acc() const449 acc_cond::mark_t get_acc() const 450 { 451 assert(!is_white()); 452 SPOT_ASSUME(acc); 453 return *acc; 454 } cumulate_acc(acc_cond::mark_t a)455 void cumulate_acc(acc_cond::mark_t a) 456 { 457 assert(!is_white()); 458 SPOT_ASSUME(acc); 459 *acc |= a; 460 } is_white() const461 bool is_white() const 462 { 463 return !is_cyan && !pc; 464 } 465 private: 466 bool is_cyan; 467 const weight* w; // point to the weight of a state in hcyan 468 hash_type* ph; //point to the main hash table 469 hcyan_type* phc; // point to the hash table hcyan 470 const state* ps; // point to the state in hcyan 471 color *pc; // point to the color of a state stored in main hash table 472 acc_cond::mark_t* acc; // point to the acc set of a state stored 473 // in main hash table or hcyan 474 }; 475 explicit_tau03_opt_search_heap(size_t)476 explicit_tau03_opt_search_heap(size_t) 477 { 478 } 479 ~explicit_tau03_opt_search_heap()480 ~explicit_tau03_opt_search_heap() 481 { 482 hcyan_type::const_iterator sc = hc.begin(); 483 while (sc != hc.end()) 484 { 485 const state* ptr = sc->first; 486 ++sc; 487 ptr->destroy(); 488 } 489 hash_type::const_iterator s = h.begin(); 490 while (s != h.end()) 491 { 492 const state* ptr = s->first; 493 ++s; 494 ptr->destroy(); 495 } 496 } 497 get_color_ref(const state * & s)498 color_ref get_color_ref(const state*& s) 499 { 500 hcyan_type::iterator ic = hc.find(s); 501 if (ic == hc.end()) 502 { 503 hash_type::iterator it = h.find(s); 504 if (it == h.end()) 505 // white state 506 return color_ref(nullptr, nullptr); 507 if (s != it->first) 508 { 509 s->destroy(); 510 s = it->first; 511 } 512 // blue or red state 513 return color_ref(&it->second.first, &it->second.second); 514 } 515 if (s != ic->first) 516 { 517 s->destroy(); 518 s = ic->first; 519 } 520 // cyan state 521 return color_ref(&h, &hc, ic->first, 522 &ic->second.first, &ic->second.second); 523 } 524 add_new_state(const state * s,color c,const weight & w)525 void add_new_state(const state* s, color c, const weight& w) 526 { 527 assert(hc.find(s) == hc.end() && h.find(s) == h.end()); 528 assert(c == CYAN); 529 (void)c; 530 hc.emplace(std::piecewise_construct, 531 std::forward_as_tuple(s), 532 std::forward_as_tuple(w, acc_cond::mark_t({}))); 533 } 534 pop_notify(const state *) const535 void pop_notify(const state*) const 536 { 537 } 538 has_been_visited(const state * s) const539 bool has_been_visited(const state* s) const 540 { 541 hcyan_type::const_iterator ic = hc.find(s); 542 if (ic == hc.end()) 543 { 544 hash_type::const_iterator it = h.find(s); 545 return (it != h.end()); 546 } 547 return true; 548 } 549 550 enum { Has_Size = 1 }; size() const551 int size() const 552 { 553 return h.size() + hc.size(); 554 } 555 556 private: 557 558 // associate to each blue and red state its color and its acceptance set 559 hash_type h; 560 // associate to each cyan state its weight and its acceptance set 561 hcyan_type hc; 562 }; 563 564 } // anonymous 565 566 emptiness_check_ptr explicit_tau03_opt_search(const const_twa_ptr & a,option_map o)567 explicit_tau03_opt_search(const const_twa_ptr& a, option_map o) 568 { 569 return SPOT_make_shared_enabled__ 570 (tau03_opt_search<explicit_tau03_opt_search_heap>, a, 0, o); 571 } 572 573 } 574