AI 24/25 Project Software
Documentation for the AI 24/25 course programming project software
Loading...
Searching...
No Matches
state_registry.h
1#ifndef DOWNWARD_STATE_REGISTRY_H
2#define DOWNWARD_STATE_REGISTRY_H
3
4#include "downward/abstract_task.h"
5#include "downward/axioms.h"
6#include "downward/state_id.h"
7
8#include "downward/algorithms/int_hash_set.h"
9#include "downward/algorithms/int_packer.h"
10#include "downward/algorithms/segmented_vector.h"
11#include "downward/algorithms/subscriber.h"
12#include "downward/task_utils/task_properties.h"
13#include "downward/utils/hash.h"
14
15#include <set>
16#include <vector>
17
18/*
19 Overview of classes relevant to storing and working with registered states.
20
21 State
22 Objects of this class can represent registered or unregistered states.
23 Registered states contain a pointer to the StateRegistry that created them
24 and the ID they have there. Using this data, states can be used to index
25 PerStateInformation objects.
26 In addition, registered states have a pointer to the packed data of a state
27 that is stored in their registry. Values of the state can be accessed
28 through this pointer. For situations where a state's values have to be
29 accessed a lot, the state's data can be unpacked. The unpacked data is
30 stored in a vector<int> to which the state maintains a shared pointer.
31 Unregistered states contain only this unpacked data. Compared to registered
32 states, they are not guaranteed to be reachable and use no form of duplicate
33 detection.
34 Copying states is relatively cheap because the actual data does not have to
35 be copied.
36
37 StateID
38 StateIDs identify states within a state registry.
39 If the registry is known, the ID is sufficient to look up the state, which
40 is why IDs are intended for long term storage (e.g. in open lists).
41 Internally, a StateID is just an integer, so it is cheap to store and copy.
42
43 PackedStateBin (currently the same as unsigned int)
44 The actual state data is internally represented as a PackedStateBin array.
45 Each PackedStateBin can contain the values of multiple variables.
46 To minimize allocation overhead, the implementation stores the data of many
47 such states in a single large array (see SegmentedArrayVector).
48 PackedStateBin arrays are never manipulated directly but through
49 the task's state packer (see IntPacker).
50
51 -------------
52
53 StateRegistry
54 The StateRegistry allows to create states giving them an ID. IDs from
55 different state registries must not be mixed.
56 The StateRegistry also stores the actual state data in a memory friendly
57 way. It uses the following class:
58
59 SegmentedArrayVector<PackedStateBin>
60 This class is used to store the actual (packed) state data for all states
61 while avoiding dynamically allocating each state individually.
62 The index within this vector corresponds to the ID of the state.
63
64 PerStateInformation<T>
65 Associates a value of type T with every state in a given StateRegistry.
66 Can be thought of as a very compactly implemented map from State to T.
67 References stay valid as long as the state registry exists. Memory usage is
68 essentially the same as a vector<T> whose size is the number of states in
69 the registry.
70
71
72 ---------------
73 Usage example 1
74 ---------------
75 Problem:
76 A search node contains a state together with some information about how this
77 state was reached and the status of the node. The state data is already
78 stored and should not be duplicated. Open lists should in theory store
79 search nodes but we want to keep the amount of data stored in the open list to
80 a minimum.
81
82 Solution:
83
84 SearchNodeInfo
85 Remaining part of a search node besides the state that needs to be stored.
86
87 SearchNode
88 A SearchNode combines a StateID, a reference to a SearchNodeInfo and
89 OperatorCost. It is generated for easier access and not intended for long
90 term storage. The state data is only stored once an can be accessed
91 through the StateID.
92
93 SearchSpace
94 The SearchSpace uses PerStateInformation<SearchNodeInfo> to map StateIDs
95 to SearchNodeInfos. The open lists only have to store StateIDs which can be
96 used to look up a search node in the SearchSpace on demand.
97
98 ---------------
99 Usage example 2
100 ---------------
101 Problem:
102 In the landmark heuristics each state should store which landmarks are
103 already reached when this state is reached. This should only require
104 additional memory when these heuristics are used.
105
106 Solution:
107 The heuristic object uses an attribute of type PerStateBitset to store for
108 each state and each landmark whether it was reached in this state.
109*/
110namespace int_packer {
111class IntPacker;
112}
113
114using PackedStateBin = int_packer::IntPacker::Bin;
115
116class StateRegistry : public subscriber::SubscriberService<StateRegistry> {
117 struct StateIDSemanticHash {
118 const segmented_vector::SegmentedArrayVector<PackedStateBin>&
119 state_data_pool;
120 int state_size;
121 StateIDSemanticHash(
122 const segmented_vector::SegmentedArrayVector<PackedStateBin>&
123 state_data_pool,
124 int state_size)
125 : state_data_pool(state_data_pool)
126 , state_size(state_size)
127 {
128 }
129
130 int_hash_set::HashType operator()(int id) const
131 {
132 const PackedStateBin* data = state_data_pool[id];
133 utils::HashState hash_state;
134 for (int i = 0; i < state_size; ++i) {
135 hash_state.feed(data[i]);
136 }
137 return hash_state.get_hash32();
138 }
139 };
140
141 struct StateIDSemanticEqual {
142 const segmented_vector::SegmentedArrayVector<PackedStateBin>&
143 state_data_pool;
144 int state_size;
145 StateIDSemanticEqual(
146 const segmented_vector::SegmentedArrayVector<PackedStateBin>&
147 state_data_pool,
148 int state_size)
149 : state_data_pool(state_data_pool)
150 , state_size(state_size)
151 {
152 }
153
154 bool operator()(int lhs, int rhs) const
155 {
156 const PackedStateBin* lhs_data = state_data_pool[lhs];
157 const PackedStateBin* rhs_data = state_data_pool[rhs];
158 return std::equal(lhs_data, lhs_data + state_size, rhs_data);
159 }
160 };
161
162 /*
163 Hash set of StateIDs used to detect states that are already registered in
164 this registry and find their IDs. States are compared/hashed semantically,
165 i.e. the actual state data is compared, not the memory location.
166 */
167 using StateIDSet =
168 int_hash_set::IntHashSet<StateIDSemanticHash, StateIDSemanticEqual>;
169
170 PlanningTaskProxy task_proxy;
171 const int_packer::IntPacker& state_packer;
172 AxiomEvaluator& axiom_evaluator;
173 const int num_variables;
174
175 segmented_vector::SegmentedArrayVector<PackedStateBin> state_data_pool;
176 StateIDSet registered_states;
177
178 std::unique_ptr<State> cached_initial_state;
179
180 StateID insert_id_or_pop_state();
181 int get_bins_per_state() const;
182
183public:
184 explicit StateRegistry(const PlanningTaskProxy& task_proxy);
185
186 const PlanningTaskProxy& get_task_proxy() const { return task_proxy; }
187
188 int get_num_variables() const { return num_variables; }
189
190 const int_packer::IntPacker& get_state_packer() const
191 {
192 return state_packer;
193 }
194
195 /*
196 Returns the state that was registered at the given ID. The ID must refer
197 to a state in this registry. Do not mix IDs from from different
198 registries.
199 */
200 State lookup_state(StateID id) const;
201
202 /*
203 Like lookup_state above, but creates a state with unpacked data,
204 moved in via state_values. It is the caller's responsibility that
205 the unpacked data matches the state's data.
206 */
207 State lookup_state(StateID id, std::vector<int>&& state_values) const;
208
209 /*
210 Returns a reference to the initial state and registers it if this was not
211 done before. The result is cached internally so subsequent calls are
212 cheap.
213 */
214 const State& get_initial_state();
215
216 /*
217 Returns the state that results from applying op to predecessor and
218 registers it if this was not done before. This is an expensive operation
219 as it includes duplicate checking.
220 */
221 State
222 get_successor_state(const State& predecessor, const OperatorProxy& op);
223
224 template <typename Effects>
225 State get_successor_state(const State& predecessor, const Effects& effects)
226 {
227 state_data_pool.push_back(predecessor.get_buffer());
228 PackedStateBin* buffer = state_data_pool[state_data_pool.size() - 1];
229 /* Experiments for issue348 showed that for tasks with axioms it's
230 faster to compute successor states using unpacked data. */
231 if (task_properties::has_axioms(task_proxy)) {
232 predecessor.unpack();
233 std::vector<int> new_values = predecessor.get_unpacked_values();
234 for (auto effect : effects) {
235 if (does_fire(effect, predecessor)) {
236 FactPair effect_pair = effect.get_fact().get_pair();
237 new_values[effect_pair.var] = effect_pair.value;
238 }
239 }
240 axiom_evaluator.evaluate(new_values);
241 for (size_t i = 0; i < new_values.size(); ++i) {
242 state_packer.set(buffer, i, new_values[i]);
243 }
244 ::StateID id = insert_id_or_pop_state();
245 return task_proxy
246 .create_state(*this, id, buffer, std::move(new_values));
247 } else {
248 for (auto effect : effects) {
249 if (does_fire(effect, predecessor)) {
250 FactPair effect_pair = effect.get_fact().get_pair();
251 state_packer.set(
252 buffer,
253 effect_pair.var,
254 effect_pair.value);
255 }
256 }
257 ::StateID id = insert_id_or_pop_state();
258 return task_proxy.create_state(*this, id, buffer);
259 }
260 }
261
262 /*
263 Returns the number of states registered so far.
264 */
265 size_t size() const { return registered_states.size(); }
266
267 int get_state_size_in_bytes() const;
268
269 void print_statistics(utils::LogProxy& log) const;
270
271 class const_iterator {
272 /*
273 We intentionally omit parts of the forward iterator concept
274 (e.g. default construction, copy assignment, post-increment)
275 to reduce boilerplate. Supported compilers may complain about
276 this, in which case we will add the missing methods.
277 */
278
279 friend class StateRegistry;
280#ifndef NDEBUG
281 const StateRegistry* registry;
282#endif
283 StateID pos;
284
285 const_iterator(const StateRegistry& registry, size_t start)
286#ifndef NDEBUG
287 : registry(&registry)
288 , pos(start)
289#else
290 : pos(start)
291#endif
292 {
293#ifdef NDEBUG
294 (void)registry;
295#endif
296 }
297
298 public:
299 using iterator_category = std::forward_iterator_tag;
300 using value_type = StateID;
301 using pointer = const StateID*;
302 using reference = const StateID&;
303 using difference_type = void;
304
305 const_iterator& operator++()
306 {
307 ++pos.value;
308 return *this;
309 }
310
311 friend bool
312 operator==(const const_iterator& lhs, const const_iterator& rhs)
313 {
314 assert(&lhs.registry == &rhs.registry);
315 return lhs.pos == rhs.pos;
316 }
317
318 StateID operator*() { return pos; }
319
320 StateID* operator->() { return &pos; }
321 };
322
323 const_iterator begin() const { return const_iterator(*this, 0); }
324
325 const_iterator end() const { return const_iterator(*this, size()); }
326};
327
328#endif // DOWNWARD_STATE_REGISTRY_H