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