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