1 // -*- coding: utf-8 -*- 2 // Copyright (C) 2014-2020 Laboratoire de Recherche et Développement 3 // de l'Epita (LRDE). 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 #include "config.h" 21 #include <spot/twaalgos/product.hh> 22 #include <spot/twa/twagraph.hh> 23 #include <spot/twaalgos/complete.hh> 24 #include <spot/twaalgos/sccinfo.hh> 25 #include <spot/twaalgos/isdet.hh> 26 #include <deque> 27 #include <unordered_map> 28 #include <spot/misc/hash.hh> 29 30 namespace spot 31 { 32 namespace 33 { 34 typedef std::pair<unsigned, unsigned> product_state; 35 36 struct product_state_hash 37 { 38 size_t operator ()spot::__anon8640cf360111::product_state_hash39 operator()(product_state s) const noexcept 40 { 41 return wang32_hash(s.first ^ wang32_hash(s.second)); 42 } 43 }; 44 45 template<typename T> 46 static product_main(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right,unsigned left_state,unsigned right_state,twa_graph_ptr & res,T merge_acc,const output_aborter * aborter)47 void product_main(const const_twa_graph_ptr& left, 48 const const_twa_graph_ptr& right, 49 unsigned left_state, 50 unsigned right_state, 51 twa_graph_ptr& res, T merge_acc, 52 const output_aborter* aborter) 53 { 54 std::unordered_map<product_state, unsigned, product_state_hash> s2n; 55 std::deque<std::pair<product_state, unsigned>> todo; 56 57 auto v = new product_states; 58 res->set_named_prop("product-states", v); 59 60 auto new_state = 61 [&](unsigned left_state, unsigned right_state) -> unsigned 62 { 63 product_state x(left_state, right_state); 64 auto p = s2n.emplace(x, 0); 65 if (p.second) // This is a new state 66 { 67 p.first->second = res->new_state(); 68 todo.emplace_back(x, p.first->second); 69 assert(p.first->second == v->size()); 70 v->emplace_back(x); 71 } 72 return p.first->second; 73 }; 74 75 res->set_init_state(new_state(left_state, right_state)); 76 if (res->acc().is_f()) 77 // Do not bother doing any work if the resulting acceptance is 78 // false. 79 return; 80 while (!todo.empty()) 81 { 82 if (aborter && aborter->too_large(res)) 83 { 84 res = nullptr; 85 return; 86 } 87 auto top = todo.front(); 88 todo.pop_front(); 89 for (auto& l: left->out(top.first.first)) 90 for (auto& r: right->out(top.first.second)) 91 { 92 auto cond = l.cond & r.cond; 93 if (cond == bddfalse) 94 continue; 95 auto dst = new_state(l.dst, r.dst); 96 res->new_edge(top.second, dst, cond, 97 merge_acc(l.acc, r.acc)); 98 // If right is deterministic, we can abort immediately! 99 } 100 } 101 } 102 103 enum acc_op { and_acc, or_acc, xor_acc, xnor_acc }; 104 105 106 static product_aux(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right,unsigned left_state,unsigned right_state,acc_op aop,const output_aborter * aborter)107 twa_graph_ptr product_aux(const const_twa_graph_ptr& left, 108 const const_twa_graph_ptr& right, 109 unsigned left_state, 110 unsigned right_state, 111 acc_op aop, 112 const output_aborter* aborter) 113 { 114 if (SPOT_UNLIKELY(!(left->is_existential() && right->is_existential()))) 115 throw std::runtime_error 116 ("product() does not support alternating automata"); 117 if (SPOT_UNLIKELY(left->get_dict() != right->get_dict())) 118 throw std::runtime_error("product: left and right automata should " 119 "share their bdd_dict"); 120 121 auto res = make_twa_graph(left->get_dict()); 122 res->copy_ap_of(left); 123 res->copy_ap_of(right); 124 125 bool leftweak = left->prop_weak().is_true(); 126 bool rightweak = right->prop_weak().is_true(); 127 // We have optimization to the standard product in case one 128 // of the arguments is weak. 129 if (leftweak || rightweak) 130 { 131 // If both automata are weak, we can restrict the result to 132 // t, f, Büchi or co-Büchi. We use co-Büchi only when 133 // t and f cannot be used, and both acceptance conditions 134 // are in {t,f,co-Büchi}. 135 if (leftweak && rightweak) 136 { 137 weak_weak: 138 res->prop_weak(true); 139 acc_cond::mark_t accmark = {0}; 140 acc_cond::mark_t rejmark = {}; 141 auto& lacc = left->acc(); 142 auto& racc = right->acc(); 143 if ((lacc.is_co_buchi() && (racc.is_co_buchi() 144 || racc.num_sets() == 0)) 145 || (lacc.num_sets() == 0 && racc.is_co_buchi())) 146 { 147 res->set_co_buchi(); 148 std::swap(accmark, rejmark); 149 } 150 else if ((aop == and_acc && lacc.is_t() && racc.is_t()) 151 || (aop == or_acc && (lacc.is_t() || racc.is_t())) 152 || (aop == xnor_acc && ((lacc.is_t() && racc.is_t()) || 153 (lacc.is_f() && racc.is_f()))) 154 || (aop == xor_acc && ((lacc.is_t() && racc.is_f()) || 155 (lacc.is_f() && racc.is_t())))) 156 { 157 res->set_acceptance(0, acc_cond::acc_code::t()); 158 accmark = {}; 159 } 160 else if ((aop == and_acc && (lacc.is_f() || racc.is_f())) 161 || (aop == or_acc && lacc.is_f() && racc.is_f()) 162 || (aop == xor_acc && ((lacc.is_t() && racc.is_t()) || 163 (lacc.is_f() && racc.is_f()))) 164 || (aop == xnor_acc && ((lacc.is_t() && racc.is_f()) || 165 (lacc.is_f() && racc.is_t())))) 166 { 167 res->set_acceptance(0, acc_cond::acc_code::f()); 168 accmark = {}; 169 } 170 else 171 { 172 res->set_buchi(); 173 } 174 switch (aop) 175 { 176 case and_acc: 177 product_main(left, right, left_state, right_state, res, 178 [&] (acc_cond::mark_t ml, acc_cond::mark_t mr) 179 { 180 if (lacc.accepting(ml) && racc.accepting(mr)) 181 return accmark; 182 else 183 return rejmark; 184 }, aborter); 185 break; 186 case or_acc: 187 product_main(left, right, left_state, right_state, res, 188 [&] (acc_cond::mark_t ml, acc_cond::mark_t mr) 189 { 190 if (lacc.accepting(ml) || racc.accepting(mr)) 191 return accmark; 192 else 193 return rejmark; 194 }, aborter); 195 break; 196 case xor_acc: 197 product_main(left, right, left_state, right_state, res, 198 [&] (acc_cond::mark_t ml, acc_cond::mark_t mr) 199 { 200 if (lacc.accepting(ml) ^ racc.accepting(mr)) 201 return accmark; 202 else 203 return rejmark; 204 }, aborter); 205 break; 206 case xnor_acc: 207 product_main(left, right, left_state, right_state, res, 208 [&] (acc_cond::mark_t ml, acc_cond::mark_t mr) 209 { 210 if (lacc.accepting(ml) == racc.accepting(mr)) 211 return accmark; 212 else 213 return rejmark; 214 }, aborter); 215 break; 216 } 217 } 218 else if (!rightweak) 219 { 220 switch (aop) 221 { 222 case and_acc: 223 { 224 auto rightunsatmark = right->acc().unsat_mark(); 225 if (!rightunsatmark.first) 226 { 227 // Left is weak. Right was not weak, but it is 228 // always accepting. We can therefore pretend 229 // that right is weak. 230 goto weak_weak; 231 } 232 res->copy_acceptance_of(right); 233 acc_cond::mark_t rejmark = rightunsatmark.second; 234 auto& lacc = left->acc(); 235 product_main(left, right, left_state, right_state, res, 236 [&] (acc_cond::mark_t ml, acc_cond::mark_t mr) 237 { 238 if (lacc.accepting(ml)) 239 return mr; 240 else 241 return rejmark; 242 }, aborter); 243 break; 244 } 245 case or_acc: 246 { 247 auto rightsatmark = right->acc().sat_mark(); 248 if (!rightsatmark.first) 249 { 250 // Left is weak. Right was not weak, but it is 251 // always rejecting. We can therefore pretend 252 // that right is weak. 253 goto weak_weak; 254 } 255 res->copy_acceptance_of(right); 256 acc_cond::mark_t accmark = rightsatmark.second; 257 auto& lacc = left->acc(); 258 product_main(left, right, left_state, right_state, res, 259 [&] (acc_cond::mark_t ml, acc_cond::mark_t mr) 260 { 261 if (!lacc.accepting(ml)) 262 return mr; 263 else 264 return accmark; 265 }, aborter); 266 break; 267 } 268 case xor_acc: 269 case xnor_acc: 270 { 271 auto rightsatmark = right->acc().sat_mark(); 272 auto rightunsatmark = right->acc().unsat_mark(); 273 if (!rightunsatmark.first || !rightsatmark.first) 274 { 275 // Left is weak. Right was not weak, but it 276 // is either always rejecting or always 277 // accepting. We can therefore pretend that 278 // right is weak. 279 goto weak_weak; 280 } 281 goto generalcase; 282 break; 283 } 284 } 285 } 286 else // right weak 287 { 288 assert(!leftweak); 289 switch (aop) 290 { 291 case and_acc: 292 { 293 auto leftunsatmark = left->acc().unsat_mark(); 294 if (!leftunsatmark.first) 295 { 296 // Right is weak. Left was not weak, but it is 297 // always accepting. We can therefore pretend 298 // that left is weak. 299 goto weak_weak; 300 } 301 res->copy_acceptance_of(left); 302 acc_cond::mark_t rejmark = leftunsatmark.second; 303 auto& racc = right->acc(); 304 product_main(left, right, left_state, right_state, res, 305 [&] (acc_cond::mark_t ml, acc_cond::mark_t mr) 306 { 307 if (racc.accepting(mr)) 308 return ml; 309 else 310 return rejmark; 311 }, aborter); 312 break; 313 } 314 case or_acc: 315 { 316 auto leftsatmark = left->acc().sat_mark(); 317 if (!leftsatmark.first) 318 { 319 // Right is weak. Left was not weak, but it is 320 // always rejecting. We can therefore pretend 321 // that left is weak. 322 goto weak_weak; 323 } 324 res->copy_acceptance_of(left); 325 acc_cond::mark_t accmark = leftsatmark.second; 326 auto& racc = right->acc(); 327 product_main(left, right, left_state, right_state, res, 328 [&] (acc_cond::mark_t ml, acc_cond::mark_t mr) 329 { 330 if (!racc.accepting(mr)) 331 return ml; 332 else 333 return accmark; 334 }, aborter); 335 336 break; 337 } 338 case xor_acc: 339 case xnor_acc: 340 { 341 auto leftsatmark = left->acc().sat_mark(); 342 auto leftunsatmark = left->acc().unsat_mark(); 343 if (!leftunsatmark.first || !leftsatmark.first) 344 { 345 // Right is weak. Left was not weak, but it 346 // is either always rejecting or always 347 // accepting. We can therefore pretend that 348 // left is weak. 349 goto weak_weak; 350 } 351 goto generalcase; 352 break; 353 } 354 } 355 } 356 } 357 else // general case 358 { 359 generalcase: 360 auto left_num = left->num_sets(); 361 auto& left_acc = left->get_acceptance(); 362 auto right_acc = right->get_acceptance() << left_num; 363 switch (aop) 364 { 365 case and_acc: 366 right_acc &= left_acc; 367 break; 368 case or_acc: 369 right_acc |= left_acc; 370 break; 371 case xor_acc: 372 { 373 auto tmp = right_acc.complement() & left_acc; 374 right_acc &= left_acc.complement(); 375 right_acc |= tmp; 376 break; 377 } 378 case xnor_acc: 379 { 380 auto tmp = right_acc.complement() & left_acc.complement(); 381 right_acc &= left_acc; 382 tmp |= right_acc; 383 std::swap(tmp, right_acc); 384 break; 385 } 386 } 387 res->set_acceptance(left_num + right->num_sets(), right_acc); 388 product_main(left, right, left_state, right_state, res, 389 [&] (acc_cond::mark_t ml, acc_cond::mark_t mr) 390 { 391 return ml | (mr << left_num); 392 }, aborter); 393 } 394 395 if (!res) // aborted 396 return nullptr; 397 398 if (res->acc().is_f()) 399 { 400 assert(res->num_edges() == 0); 401 res->prop_universal(true); 402 res->prop_complete(false); 403 res->prop_stutter_invariant(true); 404 res->prop_terminal(true); 405 res->prop_state_acc(true); 406 } 407 else 408 { 409 // The product of two non-deterministic automata could be 410 // deterministic. Likewise for non-complete automata. 411 if (left->prop_universal() && right->prop_universal()) 412 res->prop_universal(true); 413 if (left->prop_complete() && right->prop_complete()) 414 res->prop_complete(true); 415 if (left->prop_stutter_invariant() && right->prop_stutter_invariant()) 416 res->prop_stutter_invariant(true); 417 if (left->prop_inherently_weak() && right->prop_inherently_weak()) 418 res->prop_inherently_weak(true); 419 if (left->prop_weak() && right->prop_weak()) 420 res->prop_weak(true); 421 if (left->prop_terminal() && right->prop_terminal()) 422 res->prop_terminal(true); 423 res->prop_state_acc(left->prop_state_acc() 424 && right->prop_state_acc()); 425 } 426 return res; 427 } 428 } 429 product(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right,unsigned left_state,unsigned right_state,const output_aborter * aborter)430 twa_graph_ptr product(const const_twa_graph_ptr& left, 431 const const_twa_graph_ptr& right, 432 unsigned left_state, 433 unsigned right_state, 434 const output_aborter* aborter) 435 { 436 return product_aux(left, right, left_state, right_state, and_acc, aborter); 437 } 438 product(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right,const output_aborter * aborter)439 twa_graph_ptr product(const const_twa_graph_ptr& left, 440 const const_twa_graph_ptr& right, 441 const output_aborter* aborter) 442 { 443 return product(left, right, 444 left->get_init_state_number(), 445 right->get_init_state_number(), aborter); 446 } 447 product_or(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right,unsigned left_state,unsigned right_state)448 twa_graph_ptr product_or(const const_twa_graph_ptr& left, 449 const const_twa_graph_ptr& right, 450 unsigned left_state, 451 unsigned right_state) 452 { 453 return product_aux(complete(left), complete(right), 454 left_state, right_state, or_acc, nullptr); 455 } 456 product_or(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right)457 twa_graph_ptr product_or(const const_twa_graph_ptr& left, 458 const const_twa_graph_ptr& right) 459 { 460 return product_or(left, right, 461 left->get_init_state_number(), 462 right->get_init_state_number()); 463 } 464 product_xor(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right)465 twa_graph_ptr product_xor(const const_twa_graph_ptr& left, 466 const const_twa_graph_ptr& right) 467 { 468 if (SPOT_UNLIKELY(!is_deterministic(left) || !is_deterministic(right))) 469 throw std::runtime_error 470 ("product_xor() only works with deterministic automata"); 471 472 return product_aux(complete(left), complete(right), 473 left->get_init_state_number(), 474 right->get_init_state_number(), 475 xor_acc, nullptr); 476 } 477 product_xnor(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right)478 twa_graph_ptr product_xnor(const const_twa_graph_ptr& left, 479 const const_twa_graph_ptr& right) 480 { 481 if (SPOT_UNLIKELY(!is_deterministic(left) || !is_deterministic(right))) 482 throw std::runtime_error 483 ("product_xnor() only works with deterministic automata"); 484 485 return product_aux(complete(left), complete(right), 486 left->get_init_state_number(), 487 right->get_init_state_number(), 488 xnor_acc, nullptr); 489 } 490 491 492 namespace 493 { 494 495 template<typename T> 496 static void product_susp_aux(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right,twa_graph_ptr res,bool and_acc,bool sync_all,acc_cond::mark_t rejmark,T merge_acc)497 product_susp_aux(const const_twa_graph_ptr& left, 498 const const_twa_graph_ptr& right, 499 twa_graph_ptr res, bool and_acc, 500 bool sync_all, acc_cond::mark_t rejmark, T merge_acc) 501 { 502 std::unordered_map<product_state, unsigned, product_state_hash> s2n; 503 std::deque<std::pair<product_state, unsigned>> todo; 504 505 scc_info si(left, 506 and_acc ? scc_info_options::TRACK_STATES_IF_FIN_USED 507 : (scc_info_options::TRACK_STATES_IF_FIN_USED 508 | scc_info_options::TRACK_SUCCS)); 509 si.determine_unknown_acceptance(); 510 511 auto new_state = 512 [&](unsigned left_state, unsigned right_state) -> unsigned 513 { 514 product_state x(left_state, right_state); 515 auto p = s2n.emplace(x, 0); 516 if (p.second) // This is a new state 517 { 518 p.first->second = res->new_state(); 519 todo.emplace_back(x, p.first->second); 520 } 521 return p.first->second; 522 }; 523 524 unsigned right_init = right->get_init_state_number(); 525 unsigned left_init = left->get_init_state_number(); 526 unsigned res_init; 527 528 auto target_scc = [&](unsigned scc) -> bool 529 { 530 return (!si.is_trivial(scc) 531 && (sync_all || si.is_accepting_scc(scc) == and_acc)); 532 }; 533 534 if (target_scc(si.scc_of(left_init))) 535 res_init = new_state(left_init, right_init); 536 else 537 res_init = new_state(left_init, -1U); 538 res->set_init_state(res_init); 539 540 bool sbacc = res->prop_state_acc().is_true(); 541 542 while (!todo.empty()) 543 { 544 auto top = todo.front(); 545 todo.pop_front(); 546 for (auto& l: left->out(top.first.first)) 547 if (!target_scc(si.scc_of(l.dst))) 548 { 549 acc_cond::mark_t right_acc = 550 (sbacc && top.first.second != -1U) 551 // This edge leaves a target SCC, but we build a 552 // state-based automaton, so make sure we still use 553 // the same acceptance marks as in the SCC we leave. 554 ? right->state_acc_sets(top.first.second) 555 : rejmark; 556 res->new_edge(top.second, new_state(l.dst, -1U), l.cond, 557 merge_acc(l.acc, right_acc)); 558 } 559 else 560 { 561 unsigned right_state = top.first.second; 562 if (top.first.second == -1U) 563 right_state = right_init; 564 for (auto& r: right->out(right_state)) 565 { 566 auto cond = l.cond & r.cond; 567 if (cond == bddfalse) 568 continue; 569 auto dst = new_state(l.dst, r.dst); 570 571 // For state-based automata, we cannot use the 572 // right-mark when entering a target SCC, because 573 // another sibling transition might be going to a 574 // non-target SCC without this mark. 575 acc_cond::mark_t right_acc = 576 (sbacc && top.first.second == -1U) ? rejmark : r.acc; 577 res->new_edge(top.second, dst, cond, 578 merge_acc(l.acc, right_acc)); 579 } 580 } 581 } 582 } 583 584 static twa_graph_ptr product_susp_main(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right,bool and_acc=true)585 product_susp_main(const const_twa_graph_ptr& left, 586 const const_twa_graph_ptr& right, 587 bool and_acc = true) 588 { 589 if (SPOT_UNLIKELY(!(left->is_existential() && right->is_existential()))) 590 throw std::runtime_error 591 ("product_susp() does not support alternating automata"); 592 if (SPOT_UNLIKELY(left->get_dict() != right->get_dict())) 593 throw std::runtime_error("product_susp(): left and right automata " 594 "should share their bdd_dict"); 595 596 auto false_or_left = [&] (bool ff) 597 { 598 if (ff) 599 { 600 auto res = make_twa_graph(left->get_dict()); 601 res->new_state(); 602 res->prop_terminal(true); 603 res->prop_stutter_invariant(true); 604 res->prop_universal(true); 605 res->prop_complete(false); 606 return res; 607 } 608 return make_twa_graph(left, twa::prop_set::all()); 609 }; 610 611 // We assume RIGHT is suspendable, but we want to deal with some 612 // trivial true/false cases so we can later assume right has 613 // more than one acceptance set. 614 // Note: suspendable with "t" acceptance = universal language. 615 if (SPOT_UNLIKELY(right->num_sets() == 0)) 616 { 617 if (and_acc) 618 return false_or_left(right->is_empty()); 619 else if (right->is_empty()) // left OR false = left 620 return make_twa_graph(left, twa::prop_set::all()); 621 else // left OR true = true 622 return make_twa_graph(right, twa::prop_set::all()); 623 } 624 625 auto res = make_twa_graph(left->get_dict()); 626 res->copy_ap_of(left); 627 res->copy_ap_of(right); 628 bool leftweak = left->prop_weak().is_true(); 629 630 res->prop_state_acc(left->prop_state_acc() && right->prop_state_acc()); 631 632 auto rightunsatmark = right->acc().unsat_mark(); 633 if (SPOT_UNLIKELY(!rightunsatmark.first)) 634 return false_or_left(and_acc); 635 acc_cond::mark_t rejmark = rightunsatmark.second; 636 637 if (leftweak) 638 { 639 res->copy_acceptance_of(right); 640 if (and_acc) 641 { 642 product_susp_aux(left, right, res, true, false, rejmark, 643 [&] (acc_cond::mark_t, 644 acc_cond::mark_t mr) 645 { 646 return mr; 647 }); 648 } 649 else 650 { 651 auto rightsatmark = right->acc().sat_mark(); 652 if (!rightsatmark.first) 653 // Right is always rejecting, no point in making a product_or 654 return make_twa_graph(left, twa::prop_set::all()); 655 acc_cond::mark_t accmark = rightsatmark.second; 656 auto& lacc = left->acc(); 657 product_susp_aux(left, right, res, false, false, rejmark, 658 [&] (acc_cond::mark_t ml, 659 acc_cond::mark_t mr) 660 { 661 if (!lacc.accepting(ml)) 662 return mr; 663 else 664 return accmark; 665 }); 666 } 667 } 668 else // general case 669 { 670 auto left_num = left->num_sets(); 671 auto right_acc = right->get_acceptance() << left_num; 672 if (and_acc) 673 right_acc &= left->get_acceptance(); 674 else 675 right_acc |= left->get_acceptance(); 676 res->set_acceptance(left_num + right->num_sets(), right_acc); 677 678 product_susp_aux(left, right, res, and_acc, !and_acc, rejmark, 679 [&] (acc_cond::mark_t ml, 680 acc_cond::mark_t mr) 681 { 682 return ml | (mr << left_num); 683 }); 684 } 685 686 // The product of two non-deterministic automata could be 687 // deterministic. Likewise for non-complete automata. 688 if (left->prop_universal() && right->prop_universal()) 689 res->prop_universal(true); 690 if (left->prop_complete() && right->prop_complete()) 691 res->prop_complete(true); 692 if (left->prop_stutter_invariant() && right->prop_stutter_invariant()) 693 res->prop_stutter_invariant(true); 694 if (left->prop_inherently_weak() && right->prop_inherently_weak()) 695 res->prop_inherently_weak(true); 696 if (left->prop_weak() && right->prop_weak()) 697 res->prop_weak(true); 698 if (left->prop_terminal() && right->prop_terminal()) 699 res->prop_terminal(true); 700 return res; 701 } 702 } 703 product_susp(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right)704 twa_graph_ptr product_susp(const const_twa_graph_ptr& left, 705 const const_twa_graph_ptr& right) 706 { 707 return product_susp_main(left, right); 708 } 709 product_or_susp(const const_twa_graph_ptr & left,const const_twa_graph_ptr & right)710 twa_graph_ptr product_or_susp(const const_twa_graph_ptr& left, 711 const const_twa_graph_ptr& right) 712 { 713 return product_susp_main(complete(left), right, false); 714 } 715 716 } 717