AI 24/25 Project Software
Documentation for the AI 24/25 course programming project software
Loading...
Searching...
No Matches
task_proxy.h
1#ifndef DOWNWARD_TASK_PROXY_H
2#define DOWNWARD_TASK_PROXY_H
3
4#include "downward/abstract_task.h"
5#include "downward/operator_id.h"
6#include "downward/state_id.h"
7#include "downward/task_id.h"
8
9#include "downward/algorithms/int_packer.h"
10#include "downward/utils/collections.h"
11#include "downward/utils/hash.h"
12#include "downward/utils/system.h"
13
14#include <cassert>
15#include <compare>
16#include <cstddef>
17#include <iterator>
18#include <ranges>
19#include <string>
20#include <variant>
21#include <vector>
22
23class AxiomsProxy;
24class AxiomEffectProxy;
25class AxiomEffectConditionsProxy;
26class AxiomEffectsProxy;
27class EffectProxy;
28class EffectConditionsProxy;
29class EffectsProxy;
30class FactProxy;
31class FactsProxy;
32class GoalsProxy;
33class OperatorEffectProxy;
34class OperatorEffectConditionsProxy;
35class OperatorEffectsProxy;
36class OperatorProxy;
37class OperatorsProxy;
38class PartialOperatorsProxy;
39class PreconditionsProxy;
40class State;
41class StateRegistry;
42class PlanningTaskProxy;
43class TaskProxy;
44class VariableProxy;
45class VariablesProxy;
46
47namespace causal_graph {
48class CausalGraph;
49}
50
51using PackedStateBin = int_packer::IntPacker::Bin;
52
53/*
54 Overview of the task interface.
55
56 The task interface is divided into two parts: a set of proxy classes
57 for accessing task information (TaskProxy, OperatorProxy, etc.) and
58 task implementations (subclasses of AbstractTask). Each proxy class
59 knows which AbstractTask it belongs to and uses its methods to retrieve
60 information about the task. RootTask is the AbstractTask that
61 encapsulates the unmodified original task that the planner received
62 as input.
63
64 Example code for creating a new task object and accessing its operators:
65
66 TaskProxy task_proxy(*g_root_task());
67 for (OperatorProxy op : task->get_operators())
68 utils::g_log << op.get_name() << endl;
69
70 Since proxy classes only store a reference to the AbstractTask and
71 some indices, they can be copied cheaply.
72
73 In addition to the lightweight proxy classes, the task interface
74 consists of the State class, which is used to hold state information
75 for TaskProxy tasks. The State class contains packed or unpacked state data
76 and shares the ownership with its copies, so it is cheap to copy but
77 expensive to create. If performance is absolutely critical, the values of a
78 state can be unpacked and accessed as a vector<int>.
79
80 For now, heuristics work with a TaskProxy that can represent a transformed
81 view of the original task. The search algorithms work on the unmodified root
82 task. We therefore need to do two conversions between the search and the
83 heuristics: converting states of the root task to states of the task used in
84 the heuristic computation and converting operators of the task used by the
85 heuristic to operators of the task used by the search for reporting preferred
86 operators.
87 These conversions are done by the Heuristic base class with
88 Heuristic::convert_ancestor_state() and Heuristic::set_preferred().
89
90 int FantasyHeuristic::compute_heuristic(const State &ancestor_state) {
91 State state = convert_ancestor_state(ancestor_state);
92 set_preferred(task->get_operators()[42]);
93 int sum = 0;
94 for (FactProxy fact : state)
95 sum += fact.get_value();
96 return sum;
97 }
98
99 For helper functions that work on task related objects, please see the
100 task_properties.h module.
101*/
102
103template <typename T>
104concept SizedSubscriptable = requires(const T c, std::size_t s) {
105 c.operator[](s);
106 { c.size() } -> std::convertible_to<std::size_t>;
107};
108
109template <SizedSubscriptable T>
110class ProxyIterator {
111 /* We store a pointer to collection instead of a reference
112 because iterators have to be copy assignable. */
113 const T* collection;
114 std::size_t pos;
115
116public:
117 using iterator_category = std::random_access_iterator_tag;
118 using value_type =
119 typename std::remove_cvref_t<decltype(std::declval<T>().operator[](
120 0U))>;
121 using difference_type = int;
122 using pointer = const value_type*;
123 using reference = value_type;
124
125 ProxyIterator() = default;
126
127 ProxyIterator(const T& collection, std::size_t pos)
128 : collection(&collection)
129 , pos(pos)
130 {
131 }
132
133 reference operator*() const { return (*collection)[pos]; }
134
135 ProxyIterator<T> operator++(int)
136 {
137 auto r = *this;
138 ++(*this);
139 return r;
140 }
141
142 ProxyIterator<T> operator--(int)
143 {
144 auto r = *this;
145 --(*this);
146 return r;
147 }
148
149 ProxyIterator<T>& operator++()
150 {
151 ++pos;
152 return *this;
153 }
154
155 ProxyIterator<T>& operator--()
156 {
157 --pos;
158 return *this;
159 }
160
161 reference operator[](difference_type n) const { return *(this + n); }
162
163 ProxyIterator<T>& operator+=(difference_type n)
164 {
165 pos += n;
166 return *this;
167 }
168
169 ProxyIterator<T>& operator-=(difference_type n)
170 {
171 pos -= n;
172 return *this;
173 }
174
175 friend ProxyIterator<T>
176 operator+(const ProxyIterator<T>& lhs, difference_type rhs)
177 {
178 return ProxyIterator<T>(*lhs.collection, lhs.pos + rhs);
179 }
180
181 friend ProxyIterator<T>
182 operator+(difference_type lhs, const ProxyIterator<T>& rhs)
183 {
184 return ProxyIterator<T>(*rhs.collection, rhs.pos + lhs);
185 }
186
187 friend ProxyIterator<T>
188 operator-(const ProxyIterator<T>& lhs, difference_type rhs)
189 {
190 return ProxyIterator<T>(*lhs.collection, lhs.pos - rhs);
191 }
192
193 friend difference_type
194 operator-(const ProxyIterator<T>& lhs, const ProxyIterator<T>& rhs)
195 {
196 return lhs.pos - rhs.pos;
197 }
198
199 friend bool
200 operator==(const ProxyIterator<T>& lhs, const ProxyIterator<T>& rhs)
201 {
202 assert(lhs.collection == rhs.collection);
203 return lhs.pos == rhs.pos;
204 }
205
206 friend auto
207 operator<=>(const ProxyIterator<T>& lhs, const ProxyIterator<T>& rhs)
208 {
209 assert(lhs.collection == rhs.collection);
210 return lhs.pos <=> rhs.pos;
211 }
212};
213
214template <typename T>
215class ProxyCollection : public std::ranges::view_interface<ProxyCollection<T>> {
216public:
217 auto begin() const
218 {
219 static_assert(std::random_access_iterator<ProxyIterator<T>>);
220 return ProxyIterator<T>(*static_cast<const T*>(this), 0);
221 }
222
223 auto end() const
224 {
225 static_assert(std::random_access_iterator<ProxyIterator<T>>);
226 return ProxyIterator<T>(*static_cast<const T*>(this), size());
227 }
228
229 std::size_t size() const { return static_cast<const T*>(this)->size(); }
230};
231
232class FactProxy {
233 const PlanningTask* task;
234 FactPair fact;
235
236public:
237 FactProxy(const PlanningTask& task, int var_id, int value);
238 FactProxy(const PlanningTask& task, const FactPair& fact);
239 ~FactProxy() = default;
240
241 VariableProxy get_variable() const;
242
243 int get_value() const { return fact.value; }
244
245 FactPair get_pair() const { return fact; }
246
247 std::string get_name() const { return task->get_fact_name(fact); }
248
249 bool operator==(const FactProxy& other) const
250 {
251 assert(task == other.task);
252 return fact == other.fact;
253 }
254
255 bool operator!=(const FactProxy& other) const { return !(*this == other); }
256
257 bool is_mutex(const FactProxy& other) const
258 {
259 return task->are_facts_mutex(fact, other.fact);
260 }
261};
262
263class FactsProxyIterator {
264 const PlanningTask* task;
265 int var_id;
266 int value;
267
268public:
269 FactsProxyIterator(const PlanningTask& task, int var_id, int value)
270 : task(&task)
271 , var_id(var_id)
272 , value(value)
273 {
274 }
275 ~FactsProxyIterator() = default;
276
277 FactProxy operator*() const { return FactProxy(*task, var_id, value); }
278
279 FactsProxyIterator& operator++()
280 {
281 assert(var_id < task->get_num_variables());
282 int num_facts = task->get_variable_domain_size(var_id);
283 assert(value < num_facts);
284 ++value;
285 if (value == num_facts) {
286 ++var_id;
287 value = 0;
288 }
289 return *this;
290 }
291
292 bool operator==(const FactsProxyIterator& other) const
293 {
294 assert(task == other.task);
295 return var_id == other.var_id && value == other.value;
296 }
297
298 bool operator!=(const FactsProxyIterator& other) const
299 {
300 return !(*this == other);
301 }
302};
303
304/*
305 Proxy class for the collection of all facts of a task.
306
307 We don't implement size() because it would not be constant-time.
308
309 FactsProxy supports iteration, e.g. for range-based for loops. This
310 iterates over all facts in order of increasing variable ID, and in
311 order of increasing value for each variable.
312*/
313class FactsProxy {
314 const PlanningTask* task;
315
316public:
317 explicit FactsProxy(const PlanningTask& task)
318 : task(&task)
319 {
320 }
321 ~FactsProxy() = default;
322
323 FactsProxyIterator begin() const { return FactsProxyIterator(*task, 0, 0); }
324
325 FactsProxyIterator end() const
326 {
327 return FactsProxyIterator(*task, task->get_num_variables(), 0);
328 }
329};
330
331class VariableProxy {
332 const PlanningTask* task;
333 int id;
334
335public:
336 VariableProxy(const PlanningTask& task, int id)
337 : task(&task)
338 , id(id)
339 {
340 }
341 ~VariableProxy() = default;
342
343 bool operator==(const VariableProxy& other) const
344 {
345 assert(task == other.task);
346 return id == other.id;
347 }
348
349 bool operator!=(const VariableProxy& other) const
350 {
351 return !(*this == other);
352 }
353
354 int get_id() const { return id; }
355
356 std::string get_name() const { return task->get_variable_name(id); }
357
358 int get_domain_size() const { return task->get_variable_domain_size(id); }
359
360 FactProxy get_fact(int index) const
361 {
362 assert(index < get_domain_size());
363 return FactProxy(*task, id, index);
364 }
365
366 bool is_derived() const
367 {
368 int axiom_layer = task->get_variable_axiom_layer(id);
369 return axiom_layer != -1;
370 }
371
372 int get_axiom_layer() const
373 {
374 int axiom_layer = task->get_variable_axiom_layer(id);
375 /*
376 This should only be called for derived variables.
377 Non-derived variables have axiom_layer == -1.
378 Use var.is_derived() to check.
379 */
380 assert(axiom_layer >= 0);
381 return axiom_layer;
382 }
383
384 int get_default_axiom_value() const
385 {
386 assert(is_derived());
387 return task->get_variable_default_axiom_value(id);
388 }
389};
390
391class VariablesProxy : public ProxyCollection<VariablesProxy> {
392 const PlanningTask* task;
393
394public:
395 explicit VariablesProxy(const PlanningTask& task)
396 : task(&task)
397 {
398 }
399 ~VariablesProxy() = default;
400
401 std::size_t size() const { return task->get_num_variables(); }
402
403 VariableProxy operator[](std::size_t index) const
404 {
405 assert(index < size());
406 return VariableProxy(*task, index);
407 }
408
409 FactsProxy get_facts() const { return FactsProxy(*task); }
410};
411
412class AxiomPreconditionsProxy
413 : public ProxyCollection<AxiomPreconditionsProxy> {
414 const PlanningTask* task;
415 int axiom_index;
416
417public:
418 AxiomPreconditionsProxy(const PlanningTask& task, int axiom_index)
419 : task(&task)
420 , axiom_index(axiom_index)
421 {
422 }
423
424 std::size_t size() const
425 {
426 return task->get_num_axiom_preconditions(axiom_index);
427 }
428
429 FactProxy operator[](std::size_t fact_index) const
430 {
431 assert(fact_index < size());
432 return FactProxy(
433 *task,
434 task->get_axiom_precondition(axiom_index, fact_index));
435 }
436};
437
438class OperatorPreconditionsProxy
439 : public ProxyCollection<OperatorPreconditionsProxy> {
440 const PlanningTask* task;
441 int op_index;
442
443public:
444 OperatorPreconditionsProxy(const PlanningTask& task, int op_index)
445 : task(&task)
446 , op_index(op_index)
447 {
448 }
449
450 std::size_t size() const
451 {
452 return task->get_num_operator_preconditions(op_index);
453 }
454
455 FactProxy operator[](std::size_t fact_index) const
456 {
457 assert(fact_index < size());
458 return FactProxy(
459 *task,
460 task->get_operator_precondition(op_index, fact_index));
461 }
462};
463
464class PreconditionsProxy : public ProxyCollection<PreconditionsProxy> {
465 std::variant<AxiomPreconditionsProxy, OperatorPreconditionsProxy> proxy;
466
467public:
468 PreconditionsProxy(AxiomPreconditionsProxy proxy)
469 : proxy(proxy)
470 {
471 }
472
473 PreconditionsProxy(OperatorPreconditionsProxy proxy)
474 : proxy(proxy)
475 {
476 }
477
478 std::size_t size() const
479 {
480 return std::visit([](const auto& arg) { return arg.size(); }, proxy);
481 }
482
483 FactProxy operator[](std::size_t fact_index) const
484 {
485 return std::visit(
486 [fact_index](const auto& arg) {
487 return arg.operator[](fact_index);
488 },
489 proxy);
490 }
491};
492
493class AxiomEffectConditionsProxy
494 : public ProxyCollection<AxiomEffectConditionsProxy> {
495 const PlanningTask* task;
496 int axiom_index;
497 int eff_index;
498
499public:
500 AxiomEffectConditionsProxy(
501 const PlanningTask& task,
502 int axiom_index,
503 int eff_index)
504 : task(&task)
505 , axiom_index(axiom_index)
506 , eff_index(eff_index)
507 {
508 }
509
510 std::size_t size() const
511 {
512 return task->get_num_axiom_effect_conditions(axiom_index, eff_index);
513 }
514
515 FactProxy operator[](std::size_t index) const
516 {
517 assert(index < size());
518 return FactProxy(
519 *task,
520 task->get_axiom_effect_condition(axiom_index, eff_index, index));
521 }
522};
523
524class OperatorEffectConditionsProxy
525 : public ProxyCollection<OperatorEffectConditionsProxy> {
526 const AbstractTask* task;
527 int op_index;
528 int eff_index;
529
530public:
531 OperatorEffectConditionsProxy(
532 const AbstractTask& task,
533 int op_index,
534 int eff_index)
535 : task(&task)
536 , op_index(op_index)
537 , eff_index(eff_index)
538 {
539 }
540
541 std::size_t size() const
542 {
543 return task->get_num_operator_effect_conditions(op_index, eff_index);
544 }
545
546 FactProxy operator[](std::size_t index) const
547 {
548 assert(index < size());
549 return FactProxy(
550 *task,
551 task->get_operator_effect_condition(op_index, eff_index, index));
552 }
553};
554
555class EffectConditionsProxy : public ProxyCollection<EffectConditionsProxy> {
556 std::variant<AxiomEffectConditionsProxy, OperatorEffectConditionsProxy>
557 proxy;
558
559public:
560 EffectConditionsProxy(AxiomEffectConditionsProxy proxy)
561 : proxy(proxy)
562 {
563 }
564
565 EffectConditionsProxy(OperatorEffectConditionsProxy proxy)
566 : proxy(proxy)
567 {
568 }
569
570 std::size_t size() const
571 {
572 return std::visit([](const auto& arg) { return arg.size(); }, proxy);
573 }
574
575 FactProxy operator[](std::size_t fact_index) const
576 {
577 return std::visit(
578 [fact_index](const auto& arg) {
579 return arg.operator[](fact_index);
580 },
581 proxy);
582 }
583};
584
585class AxiomEffectProxy {
586 const PlanningTask* task;
587 int axiom_index;
588 int eff_index;
589
590public:
591 AxiomEffectProxy(const PlanningTask& task, int axiom_index, int eff_index)
592 : task(&task)
593 , axiom_index(axiom_index)
594 , eff_index(eff_index)
595 {
596 }
597
598 AxiomEffectConditionsProxy get_conditions() const
599 {
600 return AxiomEffectConditionsProxy(*task, axiom_index, eff_index);
601 }
602
603 FactProxy get_fact() const
604 {
605 return FactProxy(*task, task->get_axiom_effect(axiom_index, eff_index));
606 }
607};
608
609class OperatorEffectProxy {
610 const AbstractTask* task;
611 int op_index;
612 int eff_index;
613
614public:
615 OperatorEffectProxy(const AbstractTask& task, int op_index, int eff_index)
616 : task(&task)
617 , op_index(op_index)
618 , eff_index(eff_index)
619 {
620 }
621
622 OperatorEffectConditionsProxy get_conditions() const
623 {
624 return OperatorEffectConditionsProxy(*task, op_index, eff_index);
625 }
626
627 FactProxy get_fact() const
628 {
629 return FactProxy(*task, task->get_operator_effect(op_index, eff_index));
630 }
631};
632
633class EffectProxy {
634 std::variant<AxiomEffectProxy, OperatorEffectProxy> proxy;
635
636public:
637 EffectProxy(AxiomEffectProxy proxy)
638 : proxy(proxy)
639 {
640 }
641
642 EffectProxy(OperatorEffectProxy proxy)
643 : proxy(proxy)
644 {
645 }
646
647 EffectConditionsProxy get_conditions() const
648 {
649 return std::visit(
650 [](const auto& arg) {
651 return EffectConditionsProxy(arg.get_conditions());
652 },
653 proxy);
654 }
655
656 FactProxy get_fact() const
657 {
658 return std::visit(
659 [](const auto& arg) { return arg.get_fact(); },
660 proxy);
661 }
662};
663
664class AxiomEffectsProxy : public ProxyCollection<AxiomEffectsProxy> {
665 const PlanningTask* task;
666 int op_index;
667
668public:
669 AxiomEffectsProxy(const PlanningTask& task, int op_index)
670 : task(&task)
671 , op_index(op_index)
672 {
673 }
674
675 std::size_t size() const { return task->get_num_axiom_effects(op_index); }
676
677 AxiomEffectProxy operator[](std::size_t eff_index) const
678 {
679 assert(eff_index < size());
680 return AxiomEffectProxy(*task, op_index, eff_index);
681 }
682};
683
684class OperatorEffectsProxy : public ProxyCollection<OperatorEffectsProxy> {
685 const AbstractTask* task;
686 int op_index;
687
688public:
689 OperatorEffectsProxy(const AbstractTask& task, int op_index)
690 : task(&task)
691 , op_index(op_index)
692 {
693 }
694
695 std::size_t size() const
696 {
697 return task->get_num_operator_effects(op_index);
698 }
699
700 OperatorEffectProxy operator[](std::size_t eff_index) const
701 {
702 assert(eff_index < size());
703 return OperatorEffectProxy(*task, op_index, eff_index);
704 }
705};
706
707class EffectsProxy : public ProxyCollection<EffectsProxy> {
708 std::variant<AxiomEffectsProxy, OperatorEffectsProxy> proxy;
709
710public:
711 EffectsProxy(AxiomEffectsProxy proxy)
712 : proxy(proxy)
713 {
714 }
715
716 EffectsProxy(OperatorEffectsProxy proxy)
717 : proxy(proxy)
718 {
719 }
720
721 std::size_t size() const
722 {
723 return std::visit([](const auto& arg) { return arg.size(); }, proxy);
724 }
725
726 EffectProxy operator[](std::size_t eff_index) const
727 {
728 return std::visit(
729 [eff_index](const auto& arg) {
730 return EffectProxy(arg.operator[](eff_index));
731 },
732 proxy);
733 }
734};
735
736class AxiomProxy {
737 const PlanningTask* task;
738 int index;
739
740public:
741 AxiomProxy(const PlanningTask& task, int index)
742 : task(&task)
743 , index(index)
744 {
745 }
746
747 bool operator==(const AxiomProxy& other) const
748 {
749 assert(task == other.task);
750 return index == other.index;
751 }
752
753 bool operator!=(const AxiomProxy& other) const { return !(*this == other); }
754
755 AxiomPreconditionsProxy get_preconditions() const
756 {
757 return AxiomPreconditionsProxy(*task, index);
758 }
759
760 AxiomEffectsProxy get_effects() const
761 {
762 return AxiomEffectsProxy(*task, index);
763 }
764
765 int get_cost() const { return 0; }
766
767 std::string get_name() const { return task->get_axiom_name(index); }
768
769 int get_id() const { return index; }
770};
771
772class PartialOperatorProxy {
773protected:
774 const PlanningTask* task;
775 int index;
776
777public:
778 PartialOperatorProxy(const PlanningTask& task, int index)
779 : task(&task)
780 , index(index)
781 {
782 }
783
784 OperatorPreconditionsProxy get_preconditions() const
785 {
786 return OperatorPreconditionsProxy(*task, index);
787 }
788
789 std::string get_name() const { return task->get_operator_name(index); }
790
791 int get_id() const { return index; }
792
793 /*
794 Eventually, this method should perhaps not be part of OperatorProxy but
795 live in a class that handles the task transformation and known about both
796 the original and the transformed task.
797 */
798 OperatorID get_ancestor_operator_id(const PlanningTask* ancestor_task) const
799 {
800 return OperatorID(task->convert_operator_index(index, ancestor_task));
801 }
802
803 friend bool operator==(
804 const PartialOperatorProxy& left,
805 const PartialOperatorProxy& right)
806 {
807 assert(left.task == right.task);
808 return left.index == right.index;
809 }
810};
811
812class OperatorProxy : public PartialOperatorProxy {
813public:
814 OperatorProxy(const AbstractTask& task, int index)
815 : PartialOperatorProxy(task, index)
816 {
817 }
818
819 OperatorEffectsProxy get_effects() const
820 {
821 return OperatorEffectsProxy(
822 *static_cast<const AbstractTask*>(task),
823 index);
824 }
825
826 int get_cost() const
827 {
828 return static_cast<const AbstractTask*>(task)->get_operator_cost(index);
829 }
830};
831
832class AxiomOrOperatorProxy {
833 std::variant<AxiomProxy, OperatorProxy> proxy;
834
835public:
836 AxiomOrOperatorProxy(AxiomProxy proxy)
837 : proxy(proxy)
838 {
839 }
840
841 AxiomOrOperatorProxy(OperatorProxy proxy)
842 : proxy(proxy)
843 {
844 }
845
846 operator AxiomProxy() const
847 {
848 assert(is_axiom());
849 return std::get<AxiomProxy>(proxy);
850 }
851
852 operator OperatorProxy() const
853 {
854 assert(!is_axiom());
855 return std::get<OperatorProxy>(proxy);
856 }
857
858 bool is_axiom() const { return proxy.index() == 0; }
859
860 bool operator==(const AxiomOrOperatorProxy& other) const
861 {
862 return proxy == other.proxy;
863 }
864
865 bool operator!=(const AxiomOrOperatorProxy& other) const
866 {
867 return proxy != other.proxy;
868 }
869
870 PreconditionsProxy get_preconditions() const
871 {
872 return std::visit(
873 [](const auto& arg) {
874 return PreconditionsProxy(arg.get_preconditions());
875 },
876 proxy);
877 }
878
879 EffectsProxy get_effects() const
880 {
881 return std::visit(
882 [](const auto& arg) { return EffectsProxy(arg.get_effects()); },
883 proxy);
884 }
885
886 int get_cost() const
887 {
888 return std::visit(
889 [](const auto& arg) { return arg.get_cost(); },
890 proxy);
891 }
892
893 std::string get_name() const
894 {
895 return std::visit(
896 [](const auto& arg) { return arg.get_name(); },
897 proxy);
898 }
899
900 int get_id() const
901 {
902 return std::visit([](const auto& arg) { return arg.get_id(); }, proxy);
903 }
904};
905
906class AxiomsProxy : public ProxyCollection<AxiomsProxy> {
907 const PlanningTask* task;
908
909public:
910 explicit AxiomsProxy(const PlanningTask& task)
911 : task(&task)
912 {
913 }
914
915 std::size_t size() const { return task->get_num_axioms(); }
916
917 AxiomProxy operator[](std::size_t index) const
918 {
919 assert(index < size());
920 return AxiomProxy(*task, index);
921 }
922};
923
924class OperatorsProxy : public ProxyCollection<OperatorsProxy> {
925 const AbstractTask* task;
926
927public:
928 explicit OperatorsProxy(const AbstractTask& task)
929 : task(&task)
930 {
931 }
932
933 std::size_t size() const { return task->get_num_operators(); }
934
935 OperatorProxy operator[](std::size_t index) const
936 {
937 assert(index < size());
938 return OperatorProxy(*task, index);
939 }
940
941 OperatorProxy operator[](OperatorID id) const
942 {
943 return (*this)[id.get_index()];
944 }
945};
946
947class PartialOperatorsProxy : public ProxyCollection<PartialOperatorsProxy> {
948 const PlanningTask* task;
949
950public:
951 explicit PartialOperatorsProxy(const PlanningTask& task)
952 : task(&task)
953 {
954 }
955
956 std::size_t size() const { return task->get_num_operators(); }
957
958 PartialOperatorProxy operator[](std::size_t index) const
959 {
960 assert(index < size());
961 return PartialOperatorProxy(*task, index);
962 }
963
964 PartialOperatorProxy operator[](OperatorID id) const
965 {
966 return (*this)[id.get_index()];
967 }
968};
969
970class GoalsProxy : public ProxyCollection<GoalsProxy> {
971 const PlanningTask* task;
972
973public:
974 explicit GoalsProxy(const PlanningTask& task)
975 : task(&task)
976 {
977 }
978
979 std::size_t size() const { return task->get_num_goals(); }
980
981 FactProxy operator[](std::size_t index) const
982 {
983 assert(index < size());
984 return FactProxy(*task, task->get_goal_fact(index));
985 }
986};
987
988template <typename Effect>
989bool does_fire(const EffectProxy& effect, const State& state);
990
991class State : public ProxyCollection<State> {
992 /*
993 TODO: We want to try out two things:
994 1. having StateID and num_variables next to each other, so that they
995 can fit into one 8-byte-aligned block.
996 2. removing num_variables altogether, getting it on demand.
997 It is not used in (very) performance-critical code.
998 Perhaps 2) has no positive influence after 1) because we would just have
999 a 4-byte gap at the end of the object. But it would probably be nice to
1000 have fewer attributes regardless.
1001 */
1002 const PlanningTask* task;
1003 const StateRegistry* registry;
1004 StateID id;
1005 const PackedStateBin* buffer;
1006 /*
1007 values is mutable because we think of it as a redundant representation
1008 of the state's contents, a kind of cache. One could argue for doing this
1009 differently (for example because some methods require unpacked data, so
1010 in some sense its presence changes the "logical state" from the program
1011 perspective. It is a bit weird to have a const function like unpack that
1012 is only called for its side effect on the object. But we decided to use
1013 const here to mean "const from the perspective of the state space
1014 semantics of the state".
1015 */
1016 mutable std::shared_ptr<std::vector<int>> values;
1017 const int_packer::IntPacker* state_packer;
1018 int num_variables;
1019
1020public:
1021 // Construct a registered state with only packed data.
1022 State(
1023 const PlanningTask& task,
1024 const StateRegistry& registry,
1025 StateID id,
1026 const PackedStateBin* buffer);
1027 // Construct a registered state with packed and unpacked data.
1028 State(
1029 const PlanningTask& task,
1030 const StateRegistry& registry,
1031 StateID id,
1032 const PackedStateBin* buffer,
1033 std::vector<int>&& values);
1034 // Construct a state with only unpacked data.
1035 State(const PlanningTask& task, std::vector<int>&& values);
1036
1037 bool operator==(const State& other) const;
1038 bool operator!=(const State& other) const;
1039
1040 /* Generate unpacked data if it is not available yet. Calling the function
1041 on a state that already has unpacked data has no effect. */
1042 void unpack() const;
1043
1044 std::size_t size() const;
1045 FactProxy operator[](std::size_t var_id) const;
1046 FactProxy operator[](VariableProxy var) const;
1047
1048 PlanningTaskProxy get_task() const;
1049
1050 /* Return a pointer to the registry in which this state is registered.
1051 If the state is not registered, return nullptr. */
1052 const StateRegistry* get_registry() const;
1053 /* Return the ID of the state within its registry. If the state is not
1054 registered, return StateID::no_state. */
1055 StateID get_id() const;
1056
1057 /* Access the unpacked values. Accessing the unpacked values in a state
1058 that doesn't have them is an error. Use unpack() to ensure the data
1059 exists. */
1060 const std::vector<int>& get_unpacked_values() const;
1061
1062 /* Access the packed values. Accessing packed values on states that do
1063 not have them (unregistered states) is an error. */
1064 const PackedStateBin* get_buffer() const;
1065
1066 /*
1067 Create a successor state with the given operator. The operator is assumed
1068 to be applicable and the precondition is not checked. This will create an
1069 unpacked, unregistered successor. If you need registered successors, use
1070 the methods of StateRegistry.
1071 Using this method on states without unpacked values is an error. Use
1072 unpack() to ensure the data exists.
1073 */
1074 State get_unregistered_successor(const OperatorProxy& op) const;
1075
1076 template <typename Effects>
1077 State get_unregistered_successor(const Effects& effects) const
1078 {
1079 assert(values);
1080 std::vector<int> new_values = get_unpacked_values();
1081
1082 for (const auto effect : effects) {
1083 if (does_fire(effect, *this)) {
1084 FactPair effect_fact = effect.get_fact().get_pair();
1085 new_values[effect_fact.var] = effect_fact.value;
1086 }
1087 }
1088
1089 this->apply_axioms(new_values);
1090 return State(*task, std::move(new_values));
1091 }
1092
1093private:
1094 void apply_axioms(std::vector<int>& values) const;
1095};
1096
1097namespace utils {
1098inline void feed(HashState& hash_state, const State& state)
1099{
1100 /*
1101 Hashing a state without unpacked data will result in an error.
1102 We don't want to unpack states implicitly, so this rules out the option
1103 of unpacking the states here on demand. Mixing hashes from packed and
1104 unpacked states would lead to logically equal states with different
1105 hashes. Hashing packed (and therefore registered) states also seems like
1106 a performance error because it's much cheaper to hash the state IDs
1107 instead.
1108 */
1109 feed(hash_state, state.get_unpacked_values());
1110}
1111} // namespace utils
1112
1113class PlanningTaskProxy {
1114protected:
1115 const PlanningTask* task;
1116
1117public:
1118 explicit PlanningTaskProxy(const PlanningTask& task)
1119 : task(&task)
1120 {
1121 }
1122 ~PlanningTaskProxy() = default;
1123
1124 void subscribe_to_task_destruction(
1125 subscriber::Subscriber<PlanningTask>* subscriber) const
1126 {
1127 task->subscribe(subscriber);
1128 }
1129
1130 TaskID get_id() const { return TaskID(task); }
1131
1132 VariablesProxy get_variables() const { return VariablesProxy(*task); }
1133
1134 PartialOperatorsProxy get_partial_operators() const
1135 {
1136 return PartialOperatorsProxy(*task);
1137 }
1138
1139 AxiomsProxy get_axioms() const { return AxiomsProxy(*task); }
1140
1141 GoalsProxy get_goals() const { return GoalsProxy(*task); }
1142
1143 State create_state(std::vector<int>&& state_values) const
1144 {
1145 return State(*task, std::move(state_values));
1146 }
1147
1148 // This method is meant to be called only by the state registry.
1149 State create_state(
1150 const StateRegistry& registry,
1151 StateID id,
1152 const PackedStateBin* buffer) const
1153 {
1154 return State(*task, registry, id, buffer);
1155 }
1156
1157 // This method is meant to be called only by the state registry.
1158 State create_state(
1159 const StateRegistry& registry,
1160 StateID id,
1161 const PackedStateBin* buffer,
1162 std::vector<int>&& state_values) const
1163 {
1164 return State(*task, registry, id, buffer, std::move(state_values));
1165 }
1166
1167 State get_initial_state() const
1168 {
1169 return create_state(task->get_initial_state_values());
1170 }
1171
1172 /*
1173 Convert a state from an ancestor task into a state of this task.
1174 The given state has to belong to a task that is an ancestor of
1175 this task in the sense that this task is the result of a sequence
1176 of task transformations on the ancestor task. If this is not the
1177 case, the function aborts.
1178
1179 Eventually, this method should perhaps not be part of TaskProxy but live
1180 in a class that handles the task transformation and knows about both the
1181 original and the transformed task.
1182 */
1183 State convert_ancestor_state(const State& ancestor_state) const
1184 {
1185 PlanningTaskProxy ancestor_task_proxy = ancestor_state.get_task();
1186 // Create a copy of the state values for the new state.
1187 ancestor_state.unpack();
1188 std::vector<int> state_values = ancestor_state.get_unpacked_values();
1189 task->convert_ancestor_state_values(
1190 state_values,
1191 ancestor_task_proxy.task);
1192 return create_state(std::move(state_values));
1193 }
1194
1195 explicit operator TaskProxy() const;
1196};
1197
1198class TaskProxy : public PlanningTaskProxy {
1199public:
1200 explicit TaskProxy(const AbstractTask& task)
1201 : PlanningTaskProxy(task)
1202 {
1203 }
1204 ~TaskProxy() = default;
1205
1206 OperatorsProxy get_operators() const
1207 {
1208 return OperatorsProxy(*static_cast<const AbstractTask*>(task));
1209 }
1210
1211 const causal_graph::CausalGraph& get_causal_graph() const;
1212};
1213
1214inline PlanningTaskProxy::operator TaskProxy() const
1215{
1216 return TaskProxy(*static_cast<const AbstractTask*>(task));
1217}
1218
1219inline FactProxy::FactProxy(const PlanningTask& task, const FactPair& fact)
1220 : task(&task)
1221 , fact(fact)
1222{
1223 assert(fact.var >= 0 && fact.var < task.get_num_variables());
1224 assert(fact.value >= 0 && fact.value < get_variable().get_domain_size());
1225}
1226
1227inline FactProxy::FactProxy(const PlanningTask& task, int var_id, int value)
1228 : FactProxy(task, FactPair(var_id, value))
1229{
1230}
1231
1232inline VariableProxy FactProxy::get_variable() const
1233{
1234 return VariableProxy(*task, fact.var);
1235}
1236
1237template <typename Effect>
1238inline bool does_fire(const Effect& effect, const State& state)
1239{
1240 for (FactProxy condition : effect.get_conditions()) {
1241 if (state[condition.get_variable()] != condition) return false;
1242 }
1243 return true;
1244}
1245
1246inline bool State::operator==(const State& other) const
1247{
1248 assert(task == other.task);
1249 if (registry != other.registry) {
1250 std::cerr << "Comparing registered states with unregistered states "
1251 << "or registered states from different registries is "
1252 << "treated as an error because it is likely not "
1253 << "intentional." << std::endl;
1254 utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR);
1255 }
1256 if (registry) {
1257 // Both states are registered and from the same registry.
1258 return id == other.id;
1259 } else {
1260 // Both states are unregistered.
1261 assert(values);
1262 assert(other.values);
1263 return *values == *other.values;
1264 }
1265}
1266
1267inline bool State::operator!=(const State& other) const
1268{
1269 return !(*this == other);
1270}
1271
1272inline void State::unpack() const
1273{
1274 if (!values) {
1275 int num_variables = size();
1276 /*
1277 A micro-benchmark in issue348 showed that constructing the vector
1278 in the required size and then assigning values was faster than the
1279 more obvious reserve/push_back. Although, the benchmark did not
1280 profile this specific code.
1281
1282 We might consider a bulk-unpack method in state_packer that could be
1283 more efficient. (One can imagine state packer to have extra data
1284 structures that exploit sequentially unpacking each entry, by doing
1285 things bin by bin.)
1286 */
1287 values = std::make_shared<std::vector<int>>(num_variables);
1288 for (int var = 0; var < num_variables; ++var) {
1289 (*values)[var] = state_packer->get(buffer, var);
1290 }
1291 }
1292}
1293
1294inline std::size_t State::size() const
1295{
1296 return num_variables;
1297}
1298
1299inline FactProxy State::operator[](std::size_t var_id) const
1300{
1301 assert(var_id < size());
1302 if (values) {
1303 return FactProxy(*task, var_id, (*values)[var_id]);
1304 } else {
1305 assert(buffer);
1306 assert(state_packer);
1307 return FactProxy(*task, var_id, state_packer->get(buffer, var_id));
1308 }
1309}
1310
1311inline FactProxy State::operator[](VariableProxy var) const
1312{
1313 return (*this)[var.get_id()];
1314}
1315
1316inline PlanningTaskProxy State::get_task() const
1317{
1318 return PlanningTaskProxy(*task);
1319}
1320
1321inline const StateRegistry* State::get_registry() const
1322{
1323 return registry;
1324}
1325
1326inline StateID State::get_id() const
1327{
1328 return id;
1329}
1330
1331inline const PackedStateBin* State::get_buffer() const
1332{
1333 /*
1334 TODO: we should profile what happens if we #ifndef NDEBUG this test here
1335 and in other places (e.g. the next method). The 'if' itself is probably
1336 not costly, but the 'cerr <<' stuff might prevent inlining.
1337 */
1338 if (!buffer) {
1339 std::cerr << "Accessing the packed values of an unregistered state is "
1340 << "treated as an error." << std::endl;
1341 utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR);
1342 }
1343 return buffer;
1344}
1345
1346inline const std::vector<int>& State::get_unpacked_values() const
1347{
1348 if (!values) {
1349 std::cerr << "Accessing the unpacked values of a state without "
1350 << "unpacking them first is treated as an error. Please "
1351 << "use State::unpack first." << std::endl;
1352 utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR);
1353 }
1354 return *values;
1355}
1356#endif // DOWNWARD_TASK_PROXY_H