Lattice Land Core Library
algorithm.hpp
Go to the documentation of this file.
1 // Copyright 2022 Pierre Talbot
2 
3 #ifndef LALA_CORE_ALGORITHM_HPP
4 #define LALA_CORE_ALGORITHM_HPP
5 
6 #include "ast.hpp"
7 
8 namespace lala {
9 
10 /** \return `true` if the formula `f` has the shape `variable <sig> constant`. */
11 template<class Allocator, class ExtendedSig>
14  return f.is(F::Seq)
15  && f.sig() == sig
16  && (f.seq(0).is(F::LV) || f.seq(0).is(F::V))
17  && f.seq(1).is_constant();
18 }
19 
20 /** \return `true` if the formula `f` has the shape `variable op integer constant`, e.g., `x < 4`. */
21 template<class Allocator, class ExtendedSig>
22 CUDA NI bool is_v_op_z(const TFormula<Allocator, ExtendedSig>& f, Sig sig) {
24  return f.is(F::Seq)
25  && f.sig() == sig
26  && (f.seq(0).is(F::LV) || f.seq(0).is(F::V))
27  && f.seq(1).is(F::Z);
28 }
29 
30 /** \return `true` if the formula `f` has the shape `variable = variable` or `variable <=> variable`. */
31 template<class Allocator, class ExtendedSig>
34  return f.is(F::Seq)
35  && (f.sig() == EQ || f.sig() == EQUIV)
36  && (f.seq(0).is(F::LV) || f.seq(0).is(F::V))
37  && (f.seq(1).is(F::LV) || f.seq(1).is(F::V));
38 }
39 
40 template<class Allocator>
41 CUDA NI TFormula<Allocator> make_v_op_z(LVar<Allocator> v, Sig sig, logic_int z, AType aty = UNTYPED, const Allocator& allocator = Allocator()) {
42  using F = TFormula<Allocator>;
43  return F::make_binary(F::make_lvar(UNTYPED, std::move(v)), sig, F::make_z(z), aty, allocator);
44 }
45 
46 template<class Allocator>
47 CUDA NI TFormula<Allocator> make_v_op_z(AVar v, Sig sig, logic_int z, AType aty = UNTYPED, const Allocator& allocator = Allocator()) {
48  using F = TFormula<Allocator>;
49  return F::make_binary(F::make_avar(v), sig, F::make_z(z), aty, allocator);
50 }
51 
52 template <class Allocator>
54  using F = TFormula<Allocator>;
55  assert(f.is(F::S) || f.is(F::Z) || f.is(F::R));
56  return f.is(F::S) ? SUPSETEQ : GEQ;
57 }
58 
59 template <class Allocator>
61  using F = TFormula<Allocator>;
62  assert(f.is(F::S) || f.is(F::Z) || f.is(F::R));
63  return f.is(F::S) ? SUBSETEQ : LEQ;
64 }
65 
66 namespace impl {
67  template<class Allocator, class ExtendedSig>
68  CUDA NI const TFormula<Allocator, ExtendedSig>& var_in_impl(const TFormula<Allocator, ExtendedSig>& f, bool& found);
69 
70  template<size_t n, class Allocator, class ExtendedSig>
71  CUDA NI const TFormula<Allocator, ExtendedSig>& find_var_in_seq(const TFormula<Allocator, ExtendedSig>& f, bool& found) {
72  const auto& children = battery::get<1>(battery::get<n>(f.data()));
73  for(int i = 0; i < children.size(); ++i) {
74  const auto& subformula = var_in_impl(children[i], found);
75  if(found) {
76  return subformula;
77  }
78  }
79  return f;
80  }
81 
82  template<class Allocator, class ExtendedSig>
83  CUDA NI const TFormula<Allocator, ExtendedSig>& var_in_impl(const TFormula<Allocator, ExtendedSig>& f, bool& found)
84  {
85  using F = TFormula<Allocator, ExtendedSig>;
86  switch(f.index()) {
87  case F::Z:
88  case F::R:
89  case F::S:
90  return f;
91  case F::V:
92  case F::E:
93  case F::LV:
94  found = true;
95  return f;
96  case F::Seq: return find_var_in_seq<F::Seq>(f, found);
97  case F::ESeq: return find_var_in_seq<F::ESeq>(f, found);
98  default: printf("var_in: formula not handled.\n"); assert(false); return f;
99  }
100  }
101 
102  template<size_t n, class F>
103  CUDA NI int num_vars_in_seq(const F& f) {
104  const auto& children = battery::get<1>(battery::get<n>(f.data()));
105  int total = 0;
106  for(int i = 0; i < children.size(); ++i) {
107  total += num_vars(children[i]);
108  }
109  return total;
110  }
111 
112  template<class F>
113  CUDA NI int num_qf_vars(const F& f, bool type_filter, AType aty);
114 
115  template<size_t n, class F>
116  CUDA NI int num_qf_vars_in_seq(const F& f, bool type_filter, AType aty) {
117  const auto& children = battery::get<1>(battery::get<n>(f.data()));
118  int total = 0;
119  for(int i = 0; i < children.size(); ++i) {
120  total += num_qf_vars(children[i], type_filter, aty);
121  }
122  return total;
123  }
124 
125  template<class F>
126  CUDA NI int num_qf_vars(const F& f, bool type_filter, AType aty) {
127  switch(f.index()) {
128  case F::E:
129  if(type_filter) {
130  return f.type() == aty ? 1 : 0;
131  }
132  else {
133  return 1;
134  }
135  case F::Seq: return impl::num_qf_vars_in_seq<F::Seq>(f, type_filter, aty);
136  case F::ESeq: return impl::num_qf_vars_in_seq<F::ESeq>(f, type_filter, aty);
137  default: return 0;
138  }
139  }
140 }
141 
142 /** \return The first variable occurring in the formula, or any other subformula if the formula does not contain a variable.
143  It returns either a logical variable, an abstract variable or a quantifier. */
144 template<typename Allocator, typename ExtendedSig>
146  bool found = false;
147  return impl::var_in_impl(f, found);
148 }
149 
150 /** \return The number of variables occurring in the formula `F` including existential quantifier, logical variables and abstract variables.
151  * Each occurrence of a variable is added up (duplicates are counted). */
152 template<class F>
153 CUDA NI int num_vars(const F& f)
154 {
155  switch(f.index()) {
156  case F::V:
157  case F::E:
158  case F::LV:
159  return 1;
160  case F::Seq: return impl::num_vars_in_seq<F::Seq>(f);
161  case F::ESeq: return impl::num_vars_in_seq<F::ESeq>(f);
162  default: return 0;
163  }
164 }
165 
166 /** \return The number of existential quantifiers. */
167 template<class F>
168 CUDA size_t num_quantified_vars(const F& f) {
169  return impl::num_qf_vars(f, false, UNTYPED);
170 }
171 
172 /** \return The number of variables occurring in an existential quantifier that have type `aty`. */
173 template<class F>
174 CUDA size_t num_quantified_vars(const F& f, AType aty) {
175  return impl::num_qf_vars(f, true, aty);
176 }
177 
178 template<class F>
179 CUDA size_t num_constraints(const F& f)
180 {
181  switch(f.index()) {
182  case F::E: return 0;
183  case F::Seq: {
184  if(f.sig() == AND) {
185  int total = 0;
186  for(int i = 0; i < f.seq().size(); ++i) {
187  total += num_constraints(f.seq(i));
188  }
189  return total;
190  }
191  }
192  default: return 1;
193  }
194 }
195 
196 template<class F>
197 CUDA NI AType type_of_conjunction(const typename F::Sequence& seq) {
198  AType ty = UNTYPED;
199  for(int i = 0; i < seq.size(); ++i) {
200  if(seq[i].type() == UNTYPED) {
201  return UNTYPED;
202  }
203  else if(ty == UNTYPED) {
204  ty = seq[i].type();
205  }
206  else if(ty != seq[i].type()) {
207  return UNTYPED;
208  }
209  }
210  return ty;
211 }
212 
213 /** Given a conjunctive formula `f` of the form \f$ c_1 \land ... \land c_n \f$, it returns a pair \f$ \langle c_i \land .. \land c_j, c_k \land ... \land c_l \rangle \f$ such that the first component contains all formulas with the type `ty`, and the second component, all other formulas. */
214 template<class F>
215 CUDA NI battery::tuple<F,F> extract_ty(const F& f, AType ty) {
216  assert(f.is(F::Seq));
217  const auto& seq = f.seq();
218  typename F::Sequence fty;
219  typename F::Sequence other;
220  for(int i = 0; i < seq.size(); ++i) {
221  // In case of nested conjunction.
222  if(seq[i].is(F::Seq) && seq[i].sig() == AND) {
223  auto r = extract_ty(seq[i], ty);
224  auto& fty_ = battery::get<0>(r).seq();
225  auto& other_ = battery::get<1>(r).seq();
226  for(int i = 0; i < fty_.size(); ++i) {
227  fty.push_back(std::move(fty_[i]));
228  }
229  for(int i = 0; i < other_.size(); ++i) {
230  other.push_back(std::move(other_[i]));
231  }
232  }
233  else if(seq[i].type() == ty) {
234  fty.push_back(seq[i]);
235  }
236  else {
237  other.push_back(seq[i]);
238  }
239  }
240  AType other_ty = type_of_conjunction<F>(other);
241  return battery::make_tuple(
242  F::make_nary(AND, std::move(fty), ty),
243  F::make_nary(AND, std::move(other), other_ty));
244 }
245 
246 template <class F>
247 CUDA NI std::optional<F> negate(const F& f);
248 
249 /** not(f1 \/ ... \/ fn) --> not(f1) /\ ... /\ not(fn)
250  not(f1 /\ ... /\ fn) --> not(f1) \/ ... \/ not(fn) */
251 template <class F>
252 CUDA NI std::optional<F> de_morgan_law(Sig sig_neg, const F& f) {
253  auto seq = f.seq();
254  typename F::Sequence neg_seq(seq.size());
255  for(int i = 0; i < f.seq().size(); ++i) {
256  auto neg_i = negate(seq[i]);
257  if(neg_i.has_value()) {
258  neg_seq[i] = *neg_i;
259  }
260  else {
261  return {};
262  }
263  }
264  return F::make_nary(sig_neg, neg_seq, f.type());
265 }
266 
267 template <class F>
268 CUDA NI std::optional<F> negate_eq(const F& f) {
269  assert(f.is_binary() && f.sig() == EQ);
270  if(f.seq(0).is(F::B)) {
271  auto b = f.seq(0);
272  b.b() = !b.b();
273  return F::make_binary(b, EQ, f.seq(1), f.type(), f.seq().get_allocator());
274  }
275  else if(f.seq(1).is(F::B)) {
276  auto b = f.seq(1);
277  b.b() = !b.b();
278  return F::make_binary(f.seq(0), EQ, b, f.type(), f.seq().get_allocator());
279  }
280  return F::make_nary(NEQ, f.seq(), f.type());
281 }
282 
283 template <class F>
284 CUDA NI std::optional<F> negate(const F& f) {
285  if(f.is_true()) {
286  return F::make_false();
287  }
288  else if(f.is_false()) {
289  return F::make_true();
290  }
291  else if(f.is_variable()) {
292  return F::make_unary(NOT, f, f.type());
293  }
294  else if(f.is(F::Seq)) {
295  Sig neg_sig;
296  switch(f.sig()) {
297  case NOT: return f.seq(0);
298  case EQ: return negate_eq(f);
299  // Total order predicates can be reversed.
300  case LEQ: neg_sig = GT; break;
301  case GEQ: neg_sig = LT; break;
302  case NEQ: neg_sig = EQ; break;
303  case LT: neg_sig = GEQ; break;
304  case GT: neg_sig = LEQ; break;
305  // Partial order predicates cannot simply be reversed.
306  case SUBSET:
307  case SUBSETEQ:
308  case SUPSET:
309  case SUPSETEQ:
310  case IN:
311  return F::make_unary(NOT, f, f.type());
312  case AND:
313  return de_morgan_law(OR, f);
314  case OR:
315  return de_morgan_law(AND, f);
316  default:
317  return {};
318  }
319  return F::make_nary(neg_sig, f.seq(), f.type());
320  }
321  return {};
322 }
323 
325  switch(sig) {
326  case EQ: return NEQ;
327  case NEQ: return EQ;
328  case LEQ: return GT;
329  case GEQ: return LT;
330  case LT: return GEQ;
331  case GT: return LEQ;
332  default: assert(0); return sig;
333  }
334 }
335 
336 /** True for the operators <=, <, >, >=, =, != */
337 template <class F>
338 CUDA NI bool is_arithmetic_comparison(const F& f) {
339  if(f.is(F::Seq)) {
340  switch(f.sig()) {
341  case LEQ:
342  case GEQ:
343  case LT:
344  case GT:
345  case EQ:
346  case NEQ:
347  return true;
348  default: break;
349  }
350  }
351  return false;
352 }
353 
354 /** True for the operators =, !=, subset, subseteq, supset, supseteq */
355 template <class F>
356 CUDA NI bool is_set_comparison(const F& f) {
357  if(f.is(F::Seq)) {
358  switch(f.sig()) {
359  case EQ:
360  case NEQ:
361  case SUBSET:
362  case SUBSETEQ:
363  case SUPSET:
364  case SUPSETEQ:
365  return true;
366  default: break;
367  }
368  }
369  return false;
370 }
371 
372 /** True for the operators <=, <, >, >=, =, !=, subset, subseteq, supset, supseteq */
373 template <class F>
374 CUDA NI bool is_comparison(const F& f) {
375  if(f.is(F::Seq)) {
376  switch(f.sig()) {
377  case LEQ:
378  case GEQ:
379  case LT:
380  case GT:
381  case EQ:
382  case NEQ:
383  case SUBSET:
384  case SUBSETEQ:
385  case SUPSET:
386  case SUPSETEQ:
387  return true;
388  default: break;
389  }
390  }
391  return false;
392 }
393 
394 /** Returns the converse of a comparison operator (see `is_comparison`). */
395 CUDA NI inline Sig converse_comparison(Sig sig) {
396  switch(sig) {
397  case LEQ: return GEQ;
398  case GEQ: return LEQ;
399  case GT: return LT;
400  case LT: return GT;
401  case EQ: return sig;
402  case NEQ: return sig;
403  case SUBSET: return SUPSET;
404  case SUBSETEQ: return SUPSETEQ;
405  case SUPSET: return SUBSET;
406  case SUPSETEQ: return SUBSETEQ;
407  default:
408  assert(false); // converse not supported for all operators.
409  break;
410  }
411  return sig;
412 }
413 
414 /** Given a formula `f`, we transform all occurrences of `AVar` into logical variables. */
415 template <class F, class Env>
416 CUDA NI void map_avar_to_lvar(F& f, const Env& env, bool erase_type = false) {
417  switch(f.index()) {
418  case F::V:
419  f = F::make_lvar(erase_type ? UNTYPED : f.v().aty(), env.name_of(f.v()));
420  break;
421  case F::Seq:
422  for(int i = 0; i < f.seq().size(); ++i) {
423  map_avar_to_lvar(f.seq(i), env, erase_type);
424  }
425  break;
426  case F::ESeq:
427  for(int i = 0; i < f.eseq().size(); ++i) {
428  map_avar_to_lvar(f.eseq(i), env, erase_type);
429  }
430  break;
431  default: break;
432  }
433 }
434 
435 namespace impl {
436  template <class F>
437  CUDA bool is_bz(const F& f) {
438  return f.is(F::Z) || f.is(F::B);
439  }
440 
441  template <class F>
442  CUDA bool is_binary_bz(const typename F::Sequence& seq) {
443  return seq.size() == 2 && is_bz(seq[0]) && is_bz(seq[1]);
444  }
445 
446  template <class F>
447  CUDA bool is_binary_z(const typename F::Sequence& seq) {
448  return seq.size() == 2 && seq[0].is(F::Z) && seq[1].is(F::Z);
449  }
450 
451  /** Evaluate the function or predicate `sig` on the sequence of constants `seq`.
452  * It only works on Boolean and integers constants for now.
453  */
454  template <class F>
455  CUDA F eval_seq(Sig sig, const typename F::Sequence& seq, AType atype) {
456  switch(sig) {
457  case AND: {
458  typename F::Sequence residual;
459  for(int i = 0; i < seq.size(); ++i) {
460  if(seq[i].is_false()) {
461  return F::make_false();
462  }
463  else if(!seq[i].is_true()) {
464  residual.push_back(seq[i]);
465  }
466  }
467  if(residual.size() == 0) {
468  return F::make_true();
469  }
470  else if(residual.size() == 1) {
471  return residual[0];
472  }
473  else {
474  return F::make_nary(AND, std::move(residual), atype);
475  }
476  }
477  case OR: {
478  typename F::Sequence residual;
479  for(int i = 0; i < seq.size(); ++i) {
480  if(seq[i].is_true()) {
481  return F::make_true();
482  }
483  else if(!seq[i].is_false()) {
484  residual.push_back(seq[i]);
485  }
486  }
487  if(residual.size() == 0) {
488  return F::make_false();
489  }
490  else if(residual.size() == 1) {
491  return residual[0];
492  }
493  else {
494  return F::make_nary(OR, std::move(residual), atype);
495  }
496  }
497  case IMPLY: {
498  if(is_binary_bz<F>(seq)) {
499  return seq[0].is_false() || seq[1].is_true() ? F::make_true() : F::make_false();
500  }
501  else if(seq.size() == 2) {
502  if(seq[0].is_true()) { return seq[1]; }
503  else if(seq[0].is_false()) { return F::make_true(); }
504  else if(seq[1].is_true()) { return F::make_true(); }
505  else if(seq[1].is_false()) {
506  auto r = negate(seq[0]);
507  if(r.has_value()) {
508  return r.value();
509  }
510  }
511  }
512  break;
513  }
514  case EQUIV: {
515  if(is_binary_bz<F>(seq)) {
516  return (seq[0].is_true() == seq[1].is_true()) ? F::make_true() : F::make_false();
517  }
518  else if(seq.size() == 2) {
519  if(seq[0].is_true()) { return seq[1]; }
520  else if(seq[0].is_false()) { return F::make_unary(NOT, seq[1], atype); }
521  else if(seq[1].is_true()) { return seq[0]; }
522  else if(seq[1].is_false()) { return F::make_unary(NOT, seq[0], atype); }
523  }
524  break;
525  }
526  case NOT: {
527  if(seq.size() == 1 && is_bz(seq[0])) {
528  return seq[0].is_true() ? F::make_false() : F::make_true();
529  }
530  if(seq.size() == 1 && seq[0].is_unary() && seq[0].sig() == NOT) {
531  return seq[0].seq(0);
532  }
533  break;
534  }
535  case XOR: {
536  if(is_binary_bz<F>(seq)) {
537  return (seq[0].is_true() != seq[1].is_true()) ? F::make_true() : F::make_false();
538  }
539  break;
540  }
541  case ITE: {
542  if(seq.size() == 3 && is_bz(seq[0])) {
543  return seq[0].is_true() ? seq[1] : seq[2];
544  }
545  break;
546  }
547  case EQ: {
548  if(seq.size() == 2 && seq[0].is_constant() && seq[1].is_constant()) {
549  return seq[0] == seq[1] ? F::make_true() : F::make_false();
550  }
551  // We detect a common pattern for equalities: x + (-y) == 0
552  if(seq.size() == 2 && seq[0].is_binary() &&
553  seq[1].is(F::Z) && seq[1].z() == 0 &&
554  seq[0].sig() == ADD &&
555  seq[0].seq(1).is_unary() && seq[0].seq(1).sig() == NEG && seq[0].seq(1).seq(0).is_variable() &&
556  seq[0].seq(0).is_variable())
557  {
558  return F::make_binary(seq[0].seq(0), EQ, seq[0].seq(1).seq(0), atype);
559  }
560  break;
561  }
562  case NEQ: {
563  if(seq.size() == 2 && seq[0].is_constant() && seq[1].is_constant()) {
564  return seq[0] != seq[1] ? F::make_true() : F::make_false();
565  }
566  // We detect a common pattern for disequalities: x + (-y) != 0
567  if(seq.size() == 2 && seq[0].is_binary() &&
568  seq[1].is(F::Z) && seq[1].z() == 0 &&
569  seq[0].sig() == ADD &&
570  seq[0].seq(1).is_unary() && seq[0].seq(1).sig() == NEG && seq[0].seq(1).seq(0).is_variable() &&
571  seq[0].seq(0).is_variable())
572  {
573  return F::make_binary(seq[0].seq(0), NEQ, seq[0].seq(1).seq(0), atype);
574  }
575  // Detect `x + k != k2` where `k` and `k2` are constants. It should be generalized.
576  if(seq.size() == 2 && seq[0].is_binary() &&
577  is_bz(seq[1]) &&
578  seq[0].sig() == ADD &&
579  is_bz(seq[0].seq(1)))
580  {
581  return F::make_binary(seq[0].seq(0), NEQ, F::make_z(seq[1].to_z() - seq[0].seq(1).to_z()), atype);
582  }
583  // k != -x --> -k != x
584  if(seq.size() == 2 && is_bz(seq[0]) && seq[1].is_unary() && seq[1].sig() == NEG && seq[1].seq(0).is_variable()) {
585  return F::make_binary(F::make_z(-seq[0].to_z()), NEQ, seq[1].seq(0), atype);
586  }
587  // -x != k --> x != -k
588  if(seq.size() == 2 && is_bz(seq[1]) && seq[0].is_unary() && seq[0].sig() == NEG && seq[0].seq(0).is_variable()) {
589  return F::make_binary(seq[0].seq(0), NEQ, F::make_z(-seq[1].to_z()), atype);
590  }
591  break;
592  }
593  case LT: {
594  if(is_binary_z<F>(seq)) {
595  return seq[0].z() < seq[1].z() ? F::make_true() : F::make_false();
596  }
597  break;
598  }
599  case LEQ: {
600  if(is_binary_z<F>(seq)) {
601  return seq[0].z() <= seq[1].z() ? F::make_true() : F::make_false();
602  }
603  break;
604  }
605  case GT: {
606  if(is_binary_z<F>(seq)) {
607  return seq[0].z() > seq[1].z() ? F::make_true() : F::make_false();
608  }
609  break;
610  }
611  case GEQ: {
612  if(is_binary_z<F>(seq)) {
613  return seq[0].z() >= seq[1].z() ? F::make_true() : F::make_false();
614  }
615  break;
616  }
617  case MIN: {
618  if(is_binary_z<F>(seq)) {
619  return F::make_z(battery::min(seq[0].z(), seq[1].z()));
620  }
621  break;
622  }
623  case MAX: {
624  if(is_binary_z<F>(seq)) {
625  return F::make_z(battery::max(seq[0].z(), seq[1].z()));
626  }
627  break;
628  }
629  case NEG: {
630  if(seq.size() == 1 && is_bz(seq[0])) {
631  return F::make_z(-seq[0].to_z());
632  }
633  if(seq.size() == 1 && seq[0].is_unary() && seq[0].sig() == NEG) {
634  return seq[0].seq(0);
635  }
636  break;
637  }
638  case ABS: {
639  if(seq.size() == 1 && is_bz(seq[0])) {
640  return F::make_z(abs(seq[0].to_z()));
641  }
642  break;
643  }
644  case ADD: {
645  typename F::Sequence residual;
646  logic_int sum = 0;
647  for(int i = 0; i < seq.size(); ++i) {
648  if(is_bz(seq[i])) {
649  sum += seq[i].to_z();
650  }
651  else {
652  residual.push_back(seq[i]);
653  }
654  }
655  if(residual.size() == 0) {
656  return F::make_z(sum);
657  }
658  else {
659  if(sum != 0) {
660  residual.push_back(F::make_z(sum));
661  }
662  if(residual.size() == 1) {
663  return residual[0];
664  }
665  else {
666  return F::make_nary(ADD, std::move(residual), atype);
667  }
668  }
669  }
670  case MUL: {
671  typename F::Sequence residual;
672  logic_int prod = 1;
673  for(int i = 0; i < seq.size(); ++i) {
674  if(is_bz(seq[i])) {
675  prod *= seq[i].to_z();
676  }
677  else {
678  residual.push_back(seq[i]);
679  }
680  }
681  if(residual.size() == 0) {
682  return F::make_z(prod);
683  }
684  else {
685  if(prod == 0) {
686  return F::make_z(0);
687  }
688  else if(prod == -1 && residual.size() > 0) {
689  residual[0] = F::make_unary(NEG, std::move(residual[0]), atype);
690  }
691  else if(prod != 1) {
692  residual.push_back(F::make_z(prod));
693  }
694  if(residual.size() == 1) {
695  return residual[0];
696  }
697  else {
698  return F::make_nary(MUL, std::move(residual), atype);
699  }
700  }
701  }
702  case SUB: {
703  if(is_binary_bz<F>(seq)) {
704  return F::make_z(seq[0].to_z() - seq[1].to_z());
705  }
706  else if(seq.size() == 2 && is_bz(seq[1]) && seq[1].to_z() == 0) {
707  return seq[0];
708  }
709  else if(seq.size() == 2 && is_bz(seq[0]) && seq[0].to_z() == 0) {
710  return F::make_unary(NEG, seq[1], atype);
711  }
712  break;
713  }
714  case TDIV:
715  case FDIV:
716  case CDIV:
717  case EDIV: {
718  if(is_binary_z<F>(seq)) {
719  switch(sig) {
720  case TDIV: return F::make_z(battery::tdiv(seq[0].z(), seq[1].z()));
721  case FDIV: return F::make_z(battery::fdiv(seq[0].z(), seq[1].z()));
722  case CDIV: return F::make_z(battery::cdiv(seq[0].z(), seq[1].z()));
723  case EDIV: return F::make_z(battery::ediv(seq[0].z(), seq[1].z()));
724  default: assert(0); break;
725  }
726  }
727  else if(seq.size() == 2 && is_bz(seq[0]) && seq[0].to_z() == 0) {
728  return F::make_z(0);
729  }
730  else if(seq.size() == 2 && is_bz(seq[1]) && seq[1].to_z() == 1) {
731  return seq[0];
732  }
733  break;
734  }
735  case TMOD: {
736  if(is_binary_z<F>(seq)) {
737  return F::make_z(battery::tmod(seq[0].z(), seq[1].z()));
738  }
739  break;
740  }
741  case FMOD: {
742  if(is_binary_z<F>(seq)) {
743  return F::make_z(battery::fmod(seq[0].z(), seq[1].z()));
744  }
745  break;
746  }
747  case CMOD: {
748  if(is_binary_z<F>(seq)) {
749  return F::make_z(battery::cmod(seq[0].z(), seq[1].z()));
750  }
751  break;
752  }
753  case EMOD: {
754  if(is_binary_z<F>(seq)) {
755  return F::make_z(battery::emod(seq[0].z(), seq[1].z()));
756  }
757  break;
758  }
759  case POW: {
760  if(is_binary_bz<F>(seq)) {
761  return F::make_z(battery::ipow(seq[0].to_z(), seq[1].to_z()));
762  }
763  else if(seq.size() == 2 && is_bz(seq[1]) && seq[1].to_z() == 0) {
764  return F::make_z(1);
765  }
766  break;
767  }
768  case IN: {
769  if(seq.size() == 2 && is_bz(seq[0]) && seq[1].is(F::S)) {
770  const auto& set = seq[1].s();
771  logic_int left = seq[0].to_z();
772  for(int i = 0; i < set.size(); ++i) {
773  const auto& lb = battery::get<0>(set[i]);
774  const auto& ub = battery::get<1>(set[i]);
775  if(is_bz(lb) && is_bz(ub) && left >= lb.to_z() && left <= ub.to_z()) {
776  return F::make_true();
777  }
778  }
779  return F::make_false();
780  }
781  else if(seq.size() == 2 && seq[1].is(F::S) && seq[1].s().size() == 0) {
782  return F::make_false();
783  }
784  break;
785  }
786  }
787  return F::make_nary(sig, seq, atype);
788  }
789 }
790 
791 template <class F>
792 CUDA NI F eval(const F& f) {
793  switch(f.index()) {
794  case F::Z: return f;
795  case F::R: return f;
796  case F::S: return f;
797  case F::B: return f;
798  case F::V: return f;
799  case F::E: return f;
800  case F::LV: return f;
801  case F::Seq: {
802  const auto& seq = f.seq();
803  typename F::Sequence evaluated_seq(seq.get_allocator());
804  for(int i = 0; i < seq.size(); ++i) {
805  evaluated_seq.push_back(eval(seq[i]));
806  }
807  return impl::eval_seq<F>(f.sig(), evaluated_seq, f.type());
808  }
809  case F::ESeq: {
810  auto eseq = f.eseq();
811  typename F::Sequence evaluated_eseq(eseq.get_allocator());
812  for(int i = 0; i < eseq.size(); ++i) {
813  evaluated_eseq.push_back(eval(eseq[i]));
814  }
815  return F::make_nary(f.esig(), std::move(evaluated_eseq), f.type());
816  }
817  default: assert(false); return f;
818  }
819 }
820 
821 /** We do simple transformation on the formula to obtain a sort of normal form such that:
822  * 1. For all c <op> t, where `c` is a constant and `t` a term, we transform it into t <converse-op> c, whenever <op> has a converse.
823  * 2. For all t <op> v, where `v` is a constant and `t` a term (not a variable or constant), we transform it into v <converse-op> t, whenever <op> has a converse.
824  * 3. Try to push NOT inside the formula (see `negate`).
825  * 4. Transform `not x` into `x = 0`.
826  * 5. Transform `x in {v}` into `x = v`.
827  *
828  * This avoids to repeat the same transformation in different abstract domains.
829 */
830 template <class F>
831 CUDA NI F normalize(const F& f) {
832  switch(f.index()) {
833  case F::Z:
834  case F::R:
835  case F::S:
836  case F::B:
837  case F::V:
838  case F::E:
839  case F::LV:
840  case F::ESeq: return f;
841  case F::Seq: {
842  if(f.sig() == NOT) {
843  assert(f.seq().size() == 1);
844  if(f.seq(0).is_variable()) {
845  return F::make_binary(f.seq(0), EQ, F::make_z(0), f.type());
846  }
847  F fi = normalize(f.seq(0));
848  auto not_f = negate(fi);
849  if(not_f.has_value() && *not_f != f) {
850  return normalize(*not_f);
851  }
852  else {
853  return F::make_unary(NOT, std::move(fi), f.type());
854  }
855  }
856  if(f.is_binary() && is_comparison(f) && f.seq(0).is_constant()) {
857  return F::make_binary(
858  normalize(f.seq(1)), converse_comparison(f.sig()), f.seq(0),
859  f.type(), f.seq().get_allocator());
860  }
861  else if(f.is_binary() && is_comparison(f) && !f.seq(0).is_variable() && f.seq(1).is_variable()) {
862  return F::make_binary(
863  f.seq(1), converse_comparison(f.sig()), normalize(f.seq(0)),
864  f.type(), f.seq().get_allocator());
865  }
866  /** x in {v} --> x = v */
867  else if(f.is_binary() && f.sig() == IN && f.seq(1).is(F::S) && f.seq(1).s().size() == 1 && battery::get<0>(f.seq(1).s()[0]) == battery::get<1>(f.seq(1).s()[0])) {
868  return F::make_binary(f.seq(0), EQ, battery::get<0>(f.seq(1).s()[0]), f.type());
869  }
870  else {
871  typename F::Sequence normalized_seq(f.seq().get_allocator());
872  for(int i = 0; i < f.seq().size(); ++i) {
873  normalized_seq.push_back(normalize(f.seq(i)));
874  }
875  return F::make_nary(f.sig(), std::move(normalized_seq), f.type(), true);
876  }
877  }
878  default: assert(false); return f;
879  }
880 }
881 
882 namespace impl {
883 /** Given an interval occuring in a set (LogicSet), we decompose it as a formula. */
884 template <class F>
885 CUDA F itv_to_formula(const F& f, const battery::tuple<F, F>& itv, const typename F::allocator_type& alloc = typename F::allocator_type()) {
886  const auto& [l, u] = itv;
887  if(l == u) {
888  return F::make_binary(f, EQ, l, f.type(), alloc);
889  }
890  else {
891  Sig geq_sig = l.is(F::S) ? SUPSETEQ : GEQ;
892  Sig leq_sig = l.is(F::S) ? SUBSETEQ : LEQ;
893  return
894  F::make_binary(
895  F::make_binary(f, geq_sig, l, f.type(), alloc),
896  AND,
897  F::make_binary(f, leq_sig, u, f.type(), alloc),
898  f.type(),
899  alloc);
900  }
901 }
902 }
903 
904 /** Given a constraint of the form `t in S` where S is a set of intervals {[l1,u1],..,[lN,uN]}, create a disjunction where each term represents interval. */
905 template <class F>
906 CUDA F decompose_in_constraint(const F& f, const typename F::allocator_type& alloc = typename F::allocator_type()) {
907  if(f.is_binary() && f.sig() == IN && f.seq(1).is(F::S)) {
908  const auto& set = f.seq(1).s();
909  if(set.size() == 1) {
910  return impl::itv_to_formula(f.seq(0), set[0], alloc);
911  }
912  else {
913  typename F::Sequence disjunction(alloc);
914  disjunction.reserve(set.size());
915  for(size_t i = 0; i < set.size(); ++i) {
916  disjunction.push_back(impl::itv_to_formula(f.seq(0), set[i], alloc));
917  }
918  return F::make_nary(OR, std::move(disjunction), f.type());
919  }
920  }
921  return f;
922 }
923 
924 // Decompose `t != u` into a disjunction `t < u \/ t > u`.
925 template <class F>
926 CUDA F decompose_arith_neq_constraint(const F& f, const typename F::allocator_type& alloc = typename F::allocator_type()) {
927  if(f.is_binary() && f.sig() == NEQ) {
928  return F::make_binary(
929  F::make_binary(f.seq(0), LT, f.seq(1), f.type(), alloc),
930  OR,
931  F::make_binary(f.seq(0), GT, f.seq(1), f.type(), alloc),
932  f.type(), alloc);
933  }
934  return f;
935 }
936 
937 }
938 
939 #endif
Definition: ast.hpp:38
Definition: ast.hpp:247
CUDA Sig sig() const
Definition: ast.hpp:548
CUDA bool is(size_t kind) const
Definition: ast.hpp:487
CUDA const Sequence & seq() const
Definition: ast.hpp:578
::lala::B<::battery::local_memory > B
Definition: b.hpp:12
Definition: abstract_deps.hpp:14
CUDA NI bool is_v_op_constant(const TFormula< Allocator, ExtendedSig > &f, Sig sig)
Definition: algorithm.hpp:12
CUDA const TFormula< Allocator, ExtendedSig > & var_in(const TFormula< Allocator, ExtendedSig > &f)
Definition: algorithm.hpp:145
CUDA NI void map_avar_to_lvar(F &f, const Env &env, bool erase_type=false)
Definition: algorithm.hpp:416
CUDA NI battery::tuple< F, F > extract_ty(const F &f, AType ty)
Definition: algorithm.hpp:215
long long int logic_int
Definition: sort.hpp:114
CUDA size_t num_constraints(const F &f)
Definition: algorithm.hpp:179
CUDA NI Sig converse_comparison(Sig sig)
Definition: algorithm.hpp:395
CUDA NI bool is_arithmetic_comparison(const F &f)
Definition: algorithm.hpp:338
Sig
Definition: ast.hpp:94
@ XOR
Logical connector.
Definition: ast.hpp:114
@ ITE
If-then-else.
Definition: ast.hpp:115
@ NEQ
Equality relations.
Definition: ast.hpp:112
@ LEQ
Unary arithmetic function symbols.
Definition: ast.hpp:113
@ NOT
Unary arithmetic function symbols.
Definition: ast.hpp:114
@ IMPLY
Unary arithmetic function symbols.
Definition: ast.hpp:114
@ TMOD
Truncated division, present in most programming languages, is defined as , i.e., it rounds towards ze...
Definition: ast.hpp:102
@ SUPSETEQ
Set inclusion predicates.
Definition: ast.hpp:107
@ OR
Unary arithmetic function symbols.
Definition: ast.hpp:114
@ EQ
Unary arithmetic function symbols.
Definition: ast.hpp:112
@ GEQ
Unary arithmetic function symbols.
Definition: ast.hpp:113
@ ADD
Unary arithmetic function symbols.
Definition: ast.hpp:97
@ IN
Set membership predicate.
Definition: ast.hpp:108
@ POW
Unary arithmetic function symbols.
Definition: ast.hpp:97
@ MIN
Unary arithmetic function symbols.
Definition: ast.hpp:97
@ EDIV
Unary arithmetic function symbols.
Definition: ast.hpp:105
@ GT
Arithmetic comparison predicates. When applied to set, it corresponds to the lexicographic ordering o...
Definition: ast.hpp:113
@ FMOD
Floor division (Knuth D. (1972). The Art of Computer Programming, Vol 1, Fundamental Algorithms),...
Definition: ast.hpp:103
@ MAX
Binary arithmetic function symbols.
Definition: ast.hpp:97
@ LT
Unary arithmetic function symbols.
Definition: ast.hpp:113
@ TDIV
Unary arithmetic function symbols.
Definition: ast.hpp:102
@ FDIV
Unary arithmetic function symbols.
Definition: ast.hpp:103
@ SUBSETEQ
Unary arithmetic function symbols.
Definition: ast.hpp:107
@ AND
Unary arithmetic function symbols.
Definition: ast.hpp:114
@ SUBSET
Unary arithmetic function symbols.
Definition: ast.hpp:107
@ EQUIV
Unary arithmetic function symbols.
Definition: ast.hpp:114
@ ABS
Unary arithmetic function symbols.
Definition: ast.hpp:96
@ SUB
Unary arithmetic function symbols.
Definition: ast.hpp:97
@ SUPSET
Unary arithmetic function symbols.
Definition: ast.hpp:107
@ MUL
Unary arithmetic function symbols.
Definition: ast.hpp:97
@ CMOD
Ceil division is defined as . Modulus is defined as .
Definition: ast.hpp:104
@ CDIV
Unary arithmetic function symbols.
Definition: ast.hpp:104
@ NEG
Unary arithmetic function symbols.
Definition: ast.hpp:96
@ EMOD
Euclidean division (Boute T. R. (1992). The Euclidean definition of the functions div and mod)....
Definition: ast.hpp:105
CUDA NI F normalize(const F &f)
Definition: algorithm.hpp:831
CUDA NI std::optional< F > negate(const F &f)
Definition: algorithm.hpp:284
CUDA size_t num_quantified_vars(const F &f)
Definition: algorithm.hpp:168
int AType
Definition: sort.hpp:18
CUDA NI bool is_var_equality(const TFormula< Allocator, ExtendedSig > &f)
Definition: algorithm.hpp:32
CUDA NI std::optional< F > de_morgan_law(Sig sig_neg, const F &f)
Definition: algorithm.hpp:252
CUDA NI bool is_v_op_z(const TFormula< Allocator, ExtendedSig > &f, Sig sig)
Definition: algorithm.hpp:22
CUDA NI TFormula< Allocator > make_v_op_z(LVar< Allocator > v, Sig sig, logic_int z, AType aty=UNTYPED, const Allocator &allocator=Allocator())
Definition: algorithm.hpp:41
CUDA NI AType type_of_conjunction(const typename F::Sequence &seq)
Definition: algorithm.hpp:197
CUDA Sig leq_of_constant(const TFormula< Allocator > &f)
Definition: algorithm.hpp:60
CUDA NI bool is_comparison(const F &f)
Definition: algorithm.hpp:374
CUDA F decompose_arith_neq_constraint(const F &f, const typename F::allocator_type &alloc=typename F::allocator_type())
Definition: algorithm.hpp:926
CUDA Sig geq_of_constant(const TFormula< Allocator > &f)
Definition: algorithm.hpp:53
CUDA NI int num_vars(const F &f)
Definition: algorithm.hpp:153
battery::string< Allocator > LVar
Definition: ast.hpp:25
CUDA Sig negate_arithmetic_comparison(Sig sig)
Definition: algorithm.hpp:324
CUDA NI F eval(const F &f)
Definition: algorithm.hpp:792
CUDA F decompose_in_constraint(const F &f, const typename F::allocator_type &alloc=typename F::allocator_type())
Definition: algorithm.hpp:906
CUDA NI std::optional< F > negate_eq(const F &f)
Definition: algorithm.hpp:268
CUDA NI bool is_set_comparison(const F &f)
Definition: algorithm.hpp:356
#define UNTYPED
Definition: sort.hpp:21