Lattice Land Powerdomains Library
Loading...
Searching...
No Matches
split_strategy.hpp
Go to the documentation of this file.
1// Copyright 2023 Pierre Talbot
2
3#ifndef LALA_POWER_SPLIT_STRATEGY_HPP
4#define LALA_POWER_SPLIT_STRATEGY_HPP
5
6#include "battery/vector.hpp"
7#include "battery/shared_ptr.hpp"
8#include "branch.hpp"
9#include "lala/logic/logic.hpp"
10#include "lala/b.hpp"
11#include "lala/abstract_deps.hpp"
12#include <optional>
13
14namespace lala {
15
16enum class VariableOrder {
21 LARGEST,
22 // unsupported:
23 // OCCURRENCE,
24 // MOST_CONSTRAINED,
25 // MAX_REGRET,
26 // DOM_W_DEG,
27 // RANDOM
28};
29
30inline const char* string_of_variable_order(VariableOrder order) {
31 switch(order) {
32 case VariableOrder::INPUT_ORDER: return "input_order";
33 case VariableOrder::FIRST_FAIL: return "first_fail";
34 case VariableOrder::ANTI_FIRST_FAIL: return "anti_first_fail";
35 case VariableOrder::SMALLEST: return "smallest";
36 case VariableOrder::LARGEST: return "largest";
37 default: return "unknown";
38 }
39}
40
41template <class StringType>
42std::optional<VariableOrder> variable_order_of_string(const StringType& str) {
43 if(str == "input_order") {
45 }
46 else if(str == "first_fail") {
48 }
49 else if(str == "anti_first_fail") {
51 }
52 else if(str == "smallest") {
54 }
55 else if(str == "largest") {
57 }
58 else {
59 return std::nullopt;
60 }
61}
62
63enum class ValueOrder {
64 MIN,
65 MAX,
66 MEDIAN,
67 SPLIT,
69 // unsupported:
70 // INTERVAL,
71 // RANDOM,
72 // MIDDLE,
73};
74
75inline const char* string_of_value_order(ValueOrder order) {
76 switch(order) {
77 case ValueOrder::MIN: return "min";
78 case ValueOrder::MAX: return "max";
79 case ValueOrder::MEDIAN: return "median";
80 case ValueOrder::SPLIT: return "split";
81 case ValueOrder::REVERSE_SPLIT: return "reverse_split";
82 default: return "unknown";
83 }
84}
85
86template <class StringType>
87std::optional<ValueOrder> value_order_of_string(const StringType& str) {
88 if(str == "min") {
89 return ValueOrder::MIN;
90 }
91 else if(str == "max") {
92 return ValueOrder::MAX;
93 }
94 else if(str == "median") {
95 return ValueOrder::MEDIAN;
96 }
97 else if(str == "split") {
98 return ValueOrder::SPLIT;
99 }
100 else if(str == "reverse_split") {
102 }
103 else {
104 return std::nullopt;
105 }
106}
107
108/** A split strategy consists of a variable order and value order on a subset of the variables. */
109template <class Allocator>
112
115 // An empty vector of variables means we should split on the underlying store directly.
116 battery::vector<AVar, Allocator> vars;
117
121
126
127 CUDA StrategyType(VariableOrder var_order, ValueOrder val_order, battery::vector<AVar, Allocator>&& vars)
129 {}
130
132 return vars.get_allocator();
133 }
134
135 template <class StrategyType2>
138
139 template <class Alloc2>
140 friend class StrategyType;
141};
142
143template <class A, class Allocator = typename A::allocator_type>
145public:
147 using sub_type = A;
148 using sub_allocator_type = typename sub_type::allocator_type;
149 using sub_tell_type = sub_type::template tell_type<allocator_type>;
152
153 constexpr static const bool is_abstract_universe = false;
154 constexpr static const bool sequential = sub_type::sequential;
155 constexpr static const bool is_totally_ordered = false;
156 constexpr static const bool preserve_bot = true;
157 constexpr static const bool preserve_top = true;
158 // The next properties should be checked more seriously, relying on the sub-domain might be uneccessarily restrictive.
159 constexpr static const bool preserve_join = sub_type::preserve_join;
160 constexpr static const bool preserve_meet = sub_type::preserve_meet;
161 constexpr static const bool injective_concretization = sub_type::injective_concretization;
162 constexpr static const bool preserve_concrete_covers = sub_type::preserve_concrete_covers;
163
164 constexpr static const char* name = "SplitStrategy";
165
166 template <class Alloc>
190
191 template <class Alloc2>
192 using tell_type = battery::vector<StrategyType<Alloc2>, Alloc2>;
193
194 template <class A2, class Alloc2>
195 friend class SplitStrategy;
196
197private:
198 using universe_type = typename A::universe_type;
199 using LB = typename universe_type::LB;
200 using UB = typename universe_type::UB;
201
202 AType atype;
203 AType var_aty;
205 battery::vector<StrategyType<allocator_type>, allocator_type> strategies;
206 int current_strategy;
207 int next_unassigned_var;
208
209 CUDA const battery::vector<AVar, allocator_type>& current_vars() const {
210 return strategies[current_strategy].vars;
211 }
212
213 CUDA NI void move_to_next_unassigned_var() {
214 while(current_strategy < strategies.size()) {
215 const auto& vars = current_vars();
216 int n = vars.empty() ? a->vars() : vars.size();
217 while(next_unassigned_var < n) {
218 universe_type v = (*a)[vars.empty() ? next_unassigned_var : vars[next_unassigned_var].vid()];
219 if(v.lb().value() != v.ub().value()) {
220 return;
221 }
222 next_unassigned_var++;
223 }
224 current_strategy++;
225 next_unassigned_var = 0;
226 }
227 }
228
229 template <class MapFunction>
230 CUDA NI AVar var_map_fold_left(const battery::vector<AVar, allocator_type>& vars, MapFunction op) {
231 int i = next_unassigned_var;
232 int best_i = i;
233 auto best = op((*a)[vars.empty() ? i : vars[i].vid()]);
234 int n = vars.empty() ? a->vars() : vars.size();
235 for(++i; i < n; ++i) {
236 const auto& u = (*a)[vars.empty() ? i : vars[i].vid()];
237 if(u.lb().value() != u.ub().value()) {
238 if(best.meet(op(u))) {
239 best_i = i;
240 }
241 }
242 }
243 return vars.empty() ? AVar{var_aty, best_i} : vars[best_i];
244 }
245
246 CUDA AVar select_var() {
247 const auto& strat = strategies[current_strategy];
248 const auto& vars = strat.vars;
249 switch(strat.var_order) {
250 case VariableOrder::INPUT_ORDER: return vars.empty() ? AVar{var_aty, next_unassigned_var} : vars[next_unassigned_var];
251 case VariableOrder::FIRST_FAIL: return var_map_fold_left(vars, [](const universe_type& u) { return u.width().ub(); });
252 case VariableOrder::ANTI_FIRST_FAIL: return var_map_fold_left(vars, [](const universe_type& u) { return dual_bound<LB>(u.width().ub()); });
253 case VariableOrder::LARGEST: return var_map_fold_left(vars, [](const universe_type& u) { return dual_bound<LB>(u.ub()); });
254 case VariableOrder::SMALLEST: return var_map_fold_left(vars, [](const universe_type& u) { return dual_bound<UB>(u.lb()); });
255 default: printf("BUG: unsupported variable order strategy\n"); assert(false); return AVar{};
256 }
257 }
258
259 template <class U>
260 CUDA NI branch_type make_branch(AVar x, Sig left_op, Sig right_op, const U& u) {
261 if((u.is_top() && U::preserve_top) || (u.is_bot() && U::preserve_bot)) {
262 if(u.is_top()) {
263 printf("%% WARNING: Cannot currently branch on unbounded variables.\n");
264 }
265 return branch_type(get_allocator());
266 }
268 using branch_vector = battery::vector<sub_tell_type, allocator_type>;
270 auto k = u.template deinterpret<F>();
274 bool res = a->interpret_tell(F::make_binary(F::make_avar(x), left_op, k, x.aty(), get_allocator()), empty_env, left, diagnostics);
275 res &= a->interpret_tell(F::make_binary(F::make_avar(x), right_op, k, x.aty(), get_allocator()), empty_env, right, diagnostics);
276 if(res) {
277 return Branch(branch_vector({std::move(left), std::move(right)}, get_allocator()));
278 }
279 // Fallback on a more standard split search strategy.
280 // We don't print anything because it might interfere with the output (without lock).
281 else if(left_op != LEQ || right_op != GT) {
282 return make_branch(x, LEQ, GT, u);
283 }
284 else {
285 printf("%% WARNING: The subdomain does not support the underlying search strategy.\n");
286 // Diagnostics mode.
287 a->template interpret_tell<true>(F::make_binary(F::make_avar(x), left_op, k, x.aty(), get_allocator()), empty_env, left, diagnostics);
288 a->template interpret_tell<true>(F::make_binary(F::make_avar(x), right_op, k, x.aty(), get_allocator()), empty_env, right, diagnostics);
289 diagnostics.print();
290 return branch_type(get_allocator());
291 }
292 }
293
294public:
296 atype(atype), var_aty(var_aty), a(a), current_strategy(0), next_unassigned_var(0), strategies(alloc)
297 {}
298
299 template<class A2, class Alloc2, class... Allocators>
301 : atype(other.atype),
302 var_aty(other.var_aty),
303 a(deps.template clone<A>(other.a)),
304 strategies(other.strategies, deps.template get_allocator<allocator_type>()),
305 current_strategy(other.current_strategy),
306 next_unassigned_var(other.next_unassigned_var)
307 {}
308
309 CUDA AType aty() const {
310 return atype;
311 }
312
314 return strategies.get_allocator();
315 }
316
317 template <class Alloc2 = allocator_type>
319 return snapshot_type<Alloc2>{strategies.size(), current_strategy, next_unassigned_var};
320 }
321
322 template <class Alloc2 = allocator_type>
324 while(strategies.size() > snap.num_strategies) {
325 strategies.pop_back();
326 }
327 current_strategy = snap.current_strategy;
328 next_unassigned_var = snap.next_unassigned_var;
329 }
330
331 /** Restart the search from the first variable. */
332 CUDA void reset() {
333 current_strategy = 0;
334 next_unassigned_var = 0;
335 }
336
337 /** This interpretation function expects `f` to be a predicate of the form `search(VariableOrder, ValueOrder, x_1, x_2, ..., x_n)`. */
338 template <bool diagnose = false, class F, class Env, class Alloc2>
340 if(!(f.is(F::ESeq)
341 && f.eseq().size() >= 2
342 && f.esig() == "search"
343 && f.eseq()[0].is(F::ESeq) && f.eseq()[0].eseq().size() == 0
344 && f.eseq()[1].is(F::ESeq) && f.eseq()[1].eseq().size() == 0))
345 {
346 RETURN_INTERPRETATION_ERROR("SplitStrategy can only interpret predicates of the form `search(input_order, indomain_min, x1, ..., xN)`.");
347 }
349 const auto& var_order_str = f.eseq()[0].esig();
350 const auto& val_order_str = f.eseq()[1].esig();
351 if(var_order_str == "input_order") { strat.var_order = VariableOrder::INPUT_ORDER; }
352 else if(var_order_str == "first_fail") { strat.var_order = VariableOrder::FIRST_FAIL; }
353 else if(var_order_str == "anti_first_fail") { strat.var_order = VariableOrder::ANTI_FIRST_FAIL; }
354 else if(var_order_str == "smallest") { strat.var_order = VariableOrder::SMALLEST; }
355 else if(var_order_str == "largest") { strat.var_order = VariableOrder::LARGEST; }
356 else {
357 RETURN_INTERPRETATION_ERROR("This variable order strategy is unsupported.");
358 }
359 if(val_order_str == "indomain_min") { strat.val_order = ValueOrder::MIN; }
360 else if(val_order_str == "indomain_max") { strat.val_order = ValueOrder::MAX; }
361 else if(val_order_str == "indomain_median") {
362 printf("WARNING: indomain_median is not supported since we use interval domain. It is replaced by SPLIT");
363 strat.val_order = ValueOrder::SPLIT;
364 }
365 else if(val_order_str == "indomain_split") { strat.val_order = ValueOrder::SPLIT; }
366 else if(val_order_str == "indomain_reverse_split") { strat.val_order = ValueOrder::REVERSE_SPLIT; }
367 else {
368 RETURN_INTERPRETATION_ERROR("This value order strategy is unsupported.");
369 }
370 for(int i = 2; i < f.eseq().size(); ++i) {
371 if(f.eseq(i).is(F::LV)) {
372 strat.vars.push_back(AVar{});
373 if(!env.interpret(f.eseq(i), strat.vars.back(), diagnostics)) {
374 return false;
375 }
376 }
377 else if(f.eseq(i).is(F::V)) {
378 strat.vars.push_back(f.eseq(i).v());
379 }
380 else if(num_vars(f.eseq(i)) > 0) {
381 RETURN_INTERPRETATION_ERROR("The predicate `search` only supports variables or constants, but an expression containing a variable was passed to it.");
382 }
383 // Ignore constant expressions.
384 else {}
385 }
386 tell.push_back(std::move(strat));
387 return true;
388 }
389
390 template <IKind kind, bool diagnose = false, class F, class Env, class I>
392 static_assert(kind == IKind::TELL);
394 }
395
396 /** This deduce method adds new strategies, and therefore do not satisfy the PCCP model.
397 * Calling this function multiple times will create multiple strategies, that will be called in sequence along a branch of the search tree.
398 * @sequential
399 */
400 template <class Alloc2>
401 CUDA local::B deduce(const tell_type<Alloc2>& t) {
402 local::B has_changed = false;
403 for(int i = 0; i < t.size(); ++i) {
404 strategies.push_back(t[i]);
405 has_changed = true;
406 }
407 return has_changed;
408 }
409
410 /** Split the next unassigned variable according to the current strategy.
411 * If all variables of the current strategy are assigned, use the next strategy.
412 * If no strategy remains, returns an empty set of branches.
413
414 If the next unassigned variable cannot be split, for instance because the value ordering strategy maps to `bot` or `top`, an empty set of branches is returned.
415 This also means that you cannot suppose `split(a) = {}` to mean `a` is at `bot`. */
417 if(a->is_bot()) {
418 return branch_type(get_allocator());
419 }
420 move_to_next_unassigned_var();
421 if(current_strategy < strategies.size()) {
422 AVar x = select_var();
423 // printf("split on %d (", x.vid()); a->project(x).print(); printf(")\n");
424 switch(strategies[current_strategy].val_order) {
425 case ValueOrder::MIN: return make_branch(x, EQ, GT, a->project(x).lb());
426 case ValueOrder::MAX: return make_branch(x, EQ, LT, a->project(x).ub());
427 // case ValueOrder::MEDIAN: return make_branch(x, EQ, NEQ, a->project(x).median().lb());
428 case ValueOrder::SPLIT: return make_branch(x, LEQ, GT, a->project(x).median().lb());
429 case ValueOrder::REVERSE_SPLIT: return make_branch(x, GT, LEQ, a->project(x).median().lb());
430 default: printf("BUG: unsupported value order strategy\n"); assert(false); return branch_type(get_allocator());
431 }
432 }
433 else {
434 // printf("%% All variables are already assigned, we could not split anymore. It means the underlying abstract domain has not detected the satisfiability or unsatisfiability of the problem although all variables were assigned.\n");
435 return branch_type(get_allocator());
436 }
437 }
438
439 CUDA size_t num_strategies() const {
440 return strategies.size();
441 }
442
443 CUDA void push_eps_strategy(VariableOrder var_order, ValueOrder val_order) {
444 strategies.push_back(StrategyType<allocator_type>(strategies.get_allocator()));
445 for(int i = strategies.size() - 1; i > 0; --i) {
446 strategies[i] = strategies[i-1];
447 }
448 battery::vector<AVar, allocator_type> vars(strategies.get_allocator());
449 strategies[0] = StrategyType<allocator_type>(var_order, val_order, std::move(vars));
450 }
451
453 current_strategy = std::max(1, current_strategy);
454 next_unassigned_var = 0;
455 }
456
457 CUDA const auto& strategies_() const {
458 return strategies;
459 }
460};
461
462} // namespace lala
463
464#endif
Definition branch.hpp:12
Definition split_strategy.hpp:144
static constexpr const bool is_abstract_universe
Definition split_strategy.hpp:153
static constexpr const bool sequential
Definition split_strategy.hpp:154
CUDA allocator_type get_allocator() const
Definition split_strategy.hpp:313
battery::vector< StrategyType< Alloc2 >, Alloc2 > tell_type
Definition split_strategy.hpp:192
CUDA SplitStrategy(const SplitStrategy< A2, Alloc2 > &other, AbstractDeps< Allocators... > &deps)
Definition split_strategy.hpp:300
Allocator allocator_type
Definition split_strategy.hpp:146
CUDA void push_eps_strategy(VariableOrder var_order, ValueOrder val_order)
Definition split_strategy.hpp:443
static constexpr const bool is_totally_ordered
Definition split_strategy.hpp:155
sub_type::template tell_type< allocator_type > sub_tell_type
Definition split_strategy.hpp:149
CUDA local::B deduce(const tell_type< Alloc2 > &t)
Definition split_strategy.hpp:401
A sub_type
Definition split_strategy.hpp:147
CUDA NI bool interpret_tell(const F &f, Env &env, tell_type< Alloc2 > &tell, IDiagnostics &diagnostics) const
Definition split_strategy.hpp:339
typename sub_type::allocator_type sub_allocator_type
Definition split_strategy.hpp:148
static constexpr const bool preserve_concrete_covers
Definition split_strategy.hpp:162
static constexpr const bool preserve_join
Definition split_strategy.hpp:159
CUDA void skip_eps_strategy()
Definition split_strategy.hpp:452
static constexpr const char * name
Definition split_strategy.hpp:164
static constexpr const bool preserve_bot
Definition split_strategy.hpp:156
Branch< sub_tell_type, allocator_type > branch_type
Definition split_strategy.hpp:150
CUDA snapshot_type< Alloc2 > snapshot(const Alloc2 &alloc=Alloc2()) const
Definition split_strategy.hpp:318
CUDA const auto & strategies_() const
Definition split_strategy.hpp:457
CUDA AType aty() const
Definition split_strategy.hpp:309
static constexpr const bool preserve_meet
Definition split_strategy.hpp:160
CUDA SplitStrategy(AType atype, AType var_aty, abstract_ptr< A > a, const allocator_type &alloc=allocator_type())
Definition split_strategy.hpp:295
CUDA void reset()
Definition split_strategy.hpp:332
CUDA bool interpret(const F &f, Env &env, I &intermediate, IDiagnostics &diagnostics) const
Definition split_strategy.hpp:391
CUDA void restore(const snapshot_type< Alloc2 > &snap)
Definition split_strategy.hpp:323
CUDA NI branch_type split()
Definition split_strategy.hpp:416
static constexpr const bool preserve_top
Definition split_strategy.hpp:157
CUDA size_t num_strategies() const
Definition split_strategy.hpp:439
static constexpr const bool injective_concretization
Definition split_strategy.hpp:161
Definition bab.hpp:13
const char * string_of_value_order(ValueOrder order)
Definition split_strategy.hpp:75
VariableOrder
Definition split_strategy.hpp:16
std::optional< ValueOrder > value_order_of_string(const StringType &str)
Definition split_strategy.hpp:87
std::optional< VariableOrder > variable_order_of_string(const StringType &str)
Definition split_strategy.hpp:42
ValueOrder
Definition split_strategy.hpp:63
const char * string_of_variable_order(VariableOrder order)
Definition split_strategy.hpp:30
Definition split_strategy.hpp:167
snapshot_type< Alloc > & operator=(const snapshot_type< Alloc > &)=default
snapshot_type< Alloc > & operator=(snapshot_type< Alloc > &&)=default
snapshot_type(snapshot_type< Alloc > &&)=default
int next_unassigned_var
Definition split_strategy.hpp:170
CUDA snapshot_type(const SplitSnapshot &other, const Alloc &)
Definition split_strategy.hpp:184
snapshot_type(const snapshot_type< Alloc > &)=default
size_t num_strategies
Definition split_strategy.hpp:168
CUDA snapshot_type(size_t num_strategies, int current_strategy, int next_unassigned_var)
Definition split_strategy.hpp:172
int current_strategy
Definition split_strategy.hpp:169
Definition split_strategy.hpp:110
StrategyType(const StrategyType< Allocator > &)=default
ValueOrder val_order
Definition split_strategy.hpp:114
CUDA StrategyType(const StrategyType2 &other, const Allocator &alloc=Allocator{})
Definition split_strategy.hpp:136
Allocator allocator_type
Definition split_strategy.hpp:111
StrategyType & operator=(StrategyType< Allocator > &&)=default
CUDA StrategyType(const Allocator &alloc=Allocator{})
Definition split_strategy.hpp:118
StrategyType(StrategyType< Allocator > &&)=default
VariableOrder var_order
Definition split_strategy.hpp:113
CUDA StrategyType(VariableOrder var_order, ValueOrder val_order, battery::vector< AVar, Allocator > &&vars)
Definition split_strategy.hpp:127
StrategyType & operator=(const StrategyType< Allocator > &)=default
battery::vector< AVar, Allocator > vars
Definition split_strategy.hpp:116
CUDA allocator_type get_allocator() const
Definition split_strategy.hpp:131