Lattice Land Core Library
simplifier.hpp
Go to the documentation of this file.
1 // Copyright 2023 Pierre Talbot
2 
3 #ifndef LALA_CORE_SIMPLIFIER_HPP
4 #define LALA_CORE_SIMPLIFIER_HPP
5 
6 #include "logic/logic.hpp"
8 #include "abstract_deps.hpp"
9 #include "battery/dynamic_bitset.hpp"
10 
11 namespace lala {
12 
13 /** This abstract domain works at the level of logical formulas.
14  * It deduces the formula by performing a number of simplifications w.r.t. an underlying abstract domain including:
15  * 1. Removing assigned variables.
16  * 2. Removing unused variables.
17  * 3. Removing entailed formulas.
18  * 4. Removing variable equality by tracking equivalence classes.
19  *
20  * The simplified formula can be obtained by calling `deinterpret()`.
21  * Given a solution to the simplified formula, the extended model (with the variables deleted) can be obtained by calling `representative()` to obtain the representative variable of each equivalence class.
22  */
23 template<class A, class Allocator>
24 class Simplifier {
25 public:
26  using allocator_type = Allocator;
27  using sub_type = A;
28  using sub_allocator_type = typename sub_type::allocator_type;
29  using universe_type = typename sub_type::universe_type;
30  using memory_type = typename universe_type::memory_type;
32 
33  constexpr static const bool is_abstract_universe = false;
34  constexpr static const bool sequential = universe_type::sequential;
35  constexpr static const bool is_totally_ordered = false;
36  // Note that I did not define the concretization function formally yet... This is not yet an fully fledged abstract domain, we need to work more on it!
37  constexpr static const bool preserve_bot = true;
38  constexpr static const bool preserve_top = true;
39  constexpr static const bool preserve_join = true;
40  constexpr static const bool preserve_meet = true;
41  constexpr static const bool injective_concretization = true;
42  constexpr static const bool preserve_concrete_covers = true;
43  constexpr static const char* name = "Simplifier";
44 
45  template<class A2, class Alloc2>
46  friend class Simplifier;
47 
48  using formula_sequence = battery::vector<TFormula<allocator_type>, allocator_type>;
49 
50 private:
51  AType atype;
53  // We keep a copy of the variable environment in which the formula has been initially interpreted.
54  // This is necessary to project the variables and ask constraints in the subdomain during deduction.
56  // Read-only conjunctive formula, where each is treated independently.
57  formula_sequence formulas;
58  // Write-only (accessed in only 1 thread because this is not a parallel lattice entity) conjunctive formula, the main operation is a map between formulas and simplified_formulas.
59  formula_sequence simplified_formulas;
60  // eliminated_variables[i] is `true` when the variable `i` can be removed because it is assigned to a constant.
61  battery::dynamic_bitset<memory_type, allocator_type> eliminated_variables;
62  // eliminated_formulas[i] is `true` when the formula `i` is entailed.
63  battery::dynamic_bitset<memory_type, allocator_type> eliminated_formulas;
64  // `equivalence_classes[i]` contains the index of the representative variable in the equivalence class of the variable `i`.
65  battery::vector<ZUB<int, memory_type>, allocator_type> equivalence_classes;
66  // `constants[i]` contains the universe value of the representative variables `i`, aggregated by join on the values of all variables in the equivalence class.
67  battery::vector<universe_type, allocator_type> constants;
68 
69 public:
70  CUDA Simplifier(AType atype
72  , const allocator_type& alloc = allocator_type())
73  : atype(atype), sub(sub), env(alloc)
74  , formulas(alloc), simplified_formulas(alloc)
75  , eliminated_variables(alloc), eliminated_formulas(alloc)
76  , equivalence_classes(alloc), constants(alloc)
77  {}
78 
79  CUDA Simplifier(this_type&& other)
80  : atype(other.atype), sub(std::move(other.sub)), env(other.env)
81  , formulas(std::move(other.formulas)), simplified_formulas(std::move(other.simplified_formulas))
82  , eliminated_variables(std::move(other.eliminated_variables)), eliminated_formulas(std::move(other.eliminated_formulas))
83  , equivalence_classes(std::move(other.equivalence_classes)), constants(std::move(other.constants))
84  {}
85 
86  struct light_copy_tag {};
87 
88  // This return a light copy of `other`, basically just keeping the equivalence classes and the environment to be able to print solutions and call `representative`.
89  template<class A2, class Alloc2>
91  : atype(other.atype)
92  , sub(sub)
93  , env(other.env, alloc)
94  , equivalence_classes(other.equivalence_classes, alloc)
95  , constants(other.constants, alloc)
96  {}
97 
99  return formulas.get_allocator();
100  }
101 
102  CUDA AType aty() const {
103  return atype;
104  }
105 
106  /** @parallel @order-preserving @increasing */
107  CUDA local::B is_bot() const {
108  return sub->is_bot();
109  }
110 
111  /** Returns the number of variables currently represented by this abstract element. */
112  CUDA size_t vars() const {
113  return equivalence_classes.size();
114  }
115 
116  template <class Alloc>
117  struct tell_type {
118  int num_vars;
121  tell_type(const Alloc& alloc = Alloc())
122  : num_vars(0), formulas(alloc), env(nullptr)
123  {}
124  };
125 
126 public:
127  template <bool diagnose = false, class F, class Env, class Alloc2>
128  CUDA NI bool interpret_tell(const F& f, Env& env, tell_type<Alloc2>& tell, IDiagnostics& diagnostics) const {
129  if(f.is(F::E)) {
130  AVar avar;
131  if(env.interpret(f.map_atype(aty()), avar, diagnostics)) {
132  tell.num_vars++;
133  tell.env = &env;
134  return true;
135  }
136  return false;
137  }
138  else {
139  tell.formulas.push_back(f);
140  tell.env = &env;
141  return true;
142  }
143  }
144 
145  template <IKind kind, bool diagnose = false, class F, class Env, class Alloc2>
146  CUDA bool interpret(const F& f, Env& env, tell_type<Alloc2>& tell, IDiagnostics& diagnostics) const {
147  return interpret_tell<diagnose>(f, env, tell, diagnostics);
148  }
149 
150  /** @sequential */
151  template <class Alloc2>
152  CUDA bool deduce(tell_type<Alloc2>&& t) {
153  if(t.env != nullptr) { // could be nullptr if the interpreted formula is true.
154  env = *(t.env);
155  eliminated_variables.resize(t.num_vars);
156  eliminated_formulas.resize(t.formulas.size());
157  constants.resize(t.num_vars);
158  equivalence_classes.resize(t.num_vars);
159  for(int i = 0; i < equivalence_classes.size(); ++i) {
160  equivalence_classes[i].meet(local::ZUB(i));
161  }
162  formulas = std::move(t.formulas);
163  simplified_formulas.resize(formulas.size());
164  return true;
165  }
166  return false;
167  }
168 
169 private:
170  // Return the abstract variable of the subdomain from the abstract variable `x` of this domain.
171  // In the environment, all variables should have been interpreted by the sub-domain, and we assume avars[0] contains the sub abstract variable.
172  CUDA AVar to_sub_var(AVar x) const {
173  assert(env[x].avars.size() > 0);
174  return env[x].avars[0];
175  }
176 
177  CUDA AVar to_sub_var(size_t vid) const {
178  return to_sub_var(AVar{aty(), vid});
179  }
180 
181  // `f` must be a formula from `formulas`.
182  CUDA AVar var_of(const TFormula<allocator_type>& f) const {
183  using F = TFormula<allocator_type>;
184  if(f.is(F::LV)) {
185  assert(env.variable_of(f.lv()).has_value());
186  assert(env.variable_of(f.lv())->get().avar_of(aty()).has_value());
187  return env.variable_of(f.lv())->get().avar_of(aty()).value();
188  }
189  else {
190  assert(f.is(F::V));
191  assert(env[f.v()].avar_of(aty()).has_value());
192  return env[f.v()].avar_of(aty()).value();
193  }
194  }
195 
196 public:
197  /** Print the abstract universe of `vname` taking into account simplifications (representative variable and constant).
198  */
199  template <class Alloc, class Abs, class Env>
200  CUDA void print_variable(const LVar<Alloc>& vname, const Env& benv, const Abs& b) const {
201  const auto& local_var = env.variable_of(vname)->get();
202  int rep = equivalence_classes[local_var.avar_of(aty())->vid()];
203  const auto& rep_name = env.name_of(AVar{aty(), rep});
204  auto benv_variable = benv.variable_of(rep_name);
205  if(benv_variable.has_value()) {
206  benv_variable->get().sort.print_value(b.project(benv_variable->get().avars[0]));
207  }
208  else {
209  local_var.sort.print_value(constants[rep]);
210  }
211  }
212 
213 private:
214  /** \return `true` if mask[i] was changed. */
215  CUDA local::B eliminate(battery::dynamic_bitset<memory_type, allocator_type>& mask, size_t i) {
216  if(!mask.test(i)) {
217  mask.set(i, true);
218  return true;
219  }
220  return false;
221  }
222 
223  // We eliminate the representative of the variable `i` if it is a singleton.
224  CUDA local::B vdeduce(size_t i) {
225  const auto& u = sub->project(to_sub_var(i));
226  size_t j = equivalence_classes[i];
227  local::B has_changed = constants[j].meet(u);
228  if(!constants[j].is_bot() && constants[j].lb().value() == constants[j].ub().value()) {
229  has_changed |= eliminate(eliminated_variables, j);
230  }
231  return has_changed;
232  }
233 
234  CUDA local::B cons_deduce(size_t i) {
235  using F = TFormula<allocator_type>;
236  // Eliminate constraint of the form x = y, and add x,y in the same equivalence class.
237  if(is_var_equality(formulas[i])) {
238  AVar x = var_of(formulas[i].seq(0));
239  AVar y = var_of(formulas[i].seq(1));
240  local::B has_changed = equivalence_classes[x.vid()].meet(local::ZUB(equivalence_classes[y.vid()]));
241  has_changed |= equivalence_classes[y.vid()].meet(local::ZUB(equivalence_classes[x.vid()]));
242  has_changed |= eliminate(eliminated_formulas, i);
243  return has_changed;
244  }
245  else {
246  // Eliminate entailed formulas.
247  IDiagnostics diagnostics;
248  typename sub_type::template ask_type<allocator_type> ask;
249 #ifdef _MSC_VER // Avoid MSVC compiler bug. See https://stackoverflow.com/questions/77144003/use-of-template-keyword-before-dependent-template-name
250  if(sub->interpret_ask(formulas[i], env, ask, diagnostics))
251 #else
252  if(sub->template interpret_ask(formulas[i], env, ask, diagnostics))
253 #endif
254  {
255  if(sub->ask(ask)) {
256  return eliminate(eliminated_formulas, i);
257  }
258  }
259  // Replace assigned variables by constants.
260  // Note that since everything is in a fixed point loop, both the constant and the equivalence class might be updated later on.
261  // This is one of the reasons we cannot update `formulas` in-place: we would not be able to update the constant a second time (since the variable would be eliminated).
262  auto f = formulas[i].map([&](const F& f, const F& parent) {
263  if(f.is_variable()) {
264  AVar x = var_of(f);
265  if(eliminated_variables.test(x.vid())) {
266  auto k = constants[x.vid()].template deinterpret<F>();
267  if(env[x].sort.is_bool() && k.is(F::Z) && parent.is_logical()) {
268  return k.z() == 0 ? F::make_false() : F::make_true();
269  }
270  return std::move(k);
271  }
272  else if(equivalence_classes[x.vid()] != x.vid()) {
273  return F::make_lvar(UNTYPED, env.name_of(AVar{aty(), equivalence_classes[x.vid()]}));
274  }
275  return f.map_atype(UNTYPED);
276  }
277  return f;
278  });
279  f = eval(f);
280  if(f.is_true()) {
281  return eliminate(eliminated_formulas, i);
282  }
283  if(f != simplified_formulas[i]) {
284  simplified_formulas[i] = f;
285  return true;
286  }
287  return false;
288  }
289  }
290 
291 public:
292  /** We have one deduction operator per variable and one per constraint in the interpreted formula. */
293  CUDA size_t num_deductions() const {
294  return constants.size() + formulas.size();
295  }
296 
297  CUDA local::B deduce(size_t i) {
298  assert(i < num_deductions());
299  if(i < constants.size()) {
300  return vdeduce(i);
301  }
302  else {
303  return cons_deduce(i - constants.size());
304  }
305  }
306 
307  CUDA size_t num_eliminated_variables() const {
308  size_t keep = 0;
309  for(int i = 0; i < equivalence_classes.size(); ++i) {
310  if(equivalence_classes[i] == i && !eliminated_variables.test(i)) {
311  ++keep;
312  }
313  }
314  return equivalence_classes.size() - keep;
315  }
316 
317  CUDA size_t num_eliminated_formulas() const {
318  return eliminated_formulas.count();
319  }
320 
322  using F = TFormula<allocator_type>;
323  typename F::Sequence seq(get_allocator());
324 
325  if(is_bot()) {
326  return F::make_false();
327  }
328 
329  // A representative variable is eliminated if all variables in its equivalence class must be eliminated.
330  for(int i = 0; i < equivalence_classes.size(); ++i) {
331  eliminated_variables.set(equivalence_classes[i], eliminated_variables.test(equivalence_classes[i]) && eliminated_variables.test(i));
332  }
333 
334  // Deinterpret the existential quantifiers (only one per equivalence classes), and the domain of each variable.
335  for(int i = 0; i < equivalence_classes.size(); ++i) {
336  if(equivalence_classes[i] == i && !eliminated_variables.test(i)) {
337  const auto& x = env[AVar{aty(), i}];
338  seq.push_back(F::make_exists(UNTYPED, x.name, x.sort));
339  auto domain_constraint = constants[i].deinterpret(AVar(aty(), i), env, get_allocator());
340  map_avar_to_lvar(domain_constraint, env, true);
341  seq.push_back(domain_constraint);
342  }
343  }
344 
345  // Deinterpret the simplified formulas.
346  for(int i = 0; i < simplified_formulas.size(); ++i) {
347  if(!eliminated_formulas.test(i)) {
348  seq.push_back(simplified_formulas[i]);
349  }
350  }
351  if(seq.size() == 0) {
352  return F::make_true();
353  }
354  else {
355  return F::make_nary(AND, std::move(seq));
356  }
357  }
358 };
359 
360 } // namespace lala
361 
362 #endif
Definition: ast.hpp:38
Definition: arith_bound.hpp:118
Definition: b.hpp:10
Definition: diagnostics.hpp:19
Definition: simplifier.hpp:24
CUDA NI TFormula< allocator_type > deinterpret()
Definition: simplifier.hpp:321
constexpr static const bool is_totally_ordered
Definition: simplifier.hpp:35
constexpr static const bool preserve_join
Definition: simplifier.hpp:39
CUDA allocator_type get_allocator() const
Definition: simplifier.hpp:98
constexpr static const bool preserve_concrete_covers
Definition: simplifier.hpp:42
CUDA local::B deduce(size_t i)
Definition: simplifier.hpp:297
CUDA Simplifier(this_type &&other)
Definition: simplifier.hpp:79
CUDA bool deduce(tell_type< Alloc2 > &&t)
Definition: simplifier.hpp:152
Allocator allocator_type
Definition: simplifier.hpp:26
constexpr static const bool preserve_meet
Definition: simplifier.hpp:40
constexpr static const bool preserve_top
Definition: simplifier.hpp:38
constexpr static const bool is_abstract_universe
Definition: simplifier.hpp:33
A sub_type
Definition: simplifier.hpp:27
CUDA size_t vars() const
Definition: simplifier.hpp:112
CUDA local::B is_bot() const
Definition: simplifier.hpp:107
constexpr static const bool sequential
Definition: simplifier.hpp:34
constexpr static const bool injective_concretization
Definition: simplifier.hpp:41
CUDA NI bool interpret_tell(const F &f, Env &env, tell_type< Alloc2 > &tell, IDiagnostics &diagnostics) const
Definition: simplifier.hpp:128
CUDA size_t num_eliminated_variables() const
Definition: simplifier.hpp:307
battery::vector< TFormula< allocator_type >, allocator_type > formula_sequence
Definition: simplifier.hpp:48
CUDA bool interpret(const F &f, Env &env, tell_type< Alloc2 > &tell, IDiagnostics &diagnostics) const
Definition: simplifier.hpp:146
CUDA AType aty() const
Definition: simplifier.hpp:102
CUDA size_t num_deductions() const
Definition: simplifier.hpp:293
typename sub_type::allocator_type sub_allocator_type
Definition: simplifier.hpp:28
typename universe_type::memory_type memory_type
Definition: simplifier.hpp:30
constexpr static const bool preserve_bot
Definition: simplifier.hpp:37
CUDA Simplifier(AType atype, abstract_ptr< sub_type > sub, const allocator_type &alloc=allocator_type())
Definition: simplifier.hpp:70
CUDA size_t num_eliminated_formulas() const
Definition: simplifier.hpp:317
CUDA void print_variable(const LVar< Alloc > &vname, const Env &benv, const Abs &b) const
Definition: simplifier.hpp:200
CUDA Simplifier(const Simplifier< A2, Alloc2 > &other, light_copy_tag tag, abstract_ptr< sub_type > sub, const allocator_type &alloc=allocator_type())
Definition: simplifier.hpp:90
typename sub_type::universe_type universe_type
Definition: simplifier.hpp:29
constexpr static const char * name
Definition: simplifier.hpp:43
CUDA NI std::optional< std::reference_wrapper< const variable_type > > variable_of(const char *lv) const
Definition: env.hpp:457
CUDA const bstring & name_of(AVar av) const
Definition: env.hpp:496
::lala::B<::battery::local_memory > B
Definition: b.hpp:12
::lala::ZUB< int, battery::local_memory > ZUB
Definition: arith_bound.hpp:69
Definition: abstract_deps.hpp:14
CUDA NI void map_avar_to_lvar(F &f, const Env &env, bool erase_type=false)
Definition: algorithm.hpp:416
@ AND
Unary arithmetic function symbols.
Definition: ast.hpp:114
battery::shared_ptr< A, typename A::allocator_type > abstract_ptr
Definition: abstract_deps.hpp:17
int AType
Definition: sort.hpp:18
CUDA NI bool is_var_equality(const TFormula< Allocator, ExtendedSig > &f)
Definition: algorithm.hpp:32
battery::string< Allocator > LVar
Definition: ast.hpp:25
CUDA NI F eval(const F &f)
Definition: algorithm.hpp:792
#define UNTYPED
Definition: sort.hpp:21
Definition: simplifier.hpp:86
Definition: simplifier.hpp:117
tell_type(const Alloc &alloc=Alloc())
Definition: simplifier.hpp:121
int num_vars
Definition: simplifier.hpp:118
VarEnv< Alloc > * env
Definition: simplifier.hpp:120
formula_sequence formulas
Definition: simplifier.hpp:119