AI 24/25 Project Software
Documentation for the AI 24/25 course programming project software
Loading...
Searching...
No Matches
end_component_decomposition_impl.h
1#ifndef GUARD_INCLUDE_PROBFD_PREPROCESSING_END_COMPONENT_DECOMPOSITION_H
2#error "This file should only be included from end_component_decomposition.h"
3#endif
4
5#include "probfd/utils/language.h"
6
7#include "downward/utils/countdown_timer.h"
8
9#include <cassert>
10#include <ranges>
11#include <type_traits>
12
13namespace probfd::preprocessing {
14
15inline void ECDStatistics::print(std::ostream& out) const
16{
17 out << " Terminal states: " << terminals << " (" << goals
18 << " goal states, " << selfloops << " self loop states)" << std::endl;
19 out << " Singleton SCC(s): " << sccs1 << " (" << sccs1_dead << " dead)"
20 << std::endl;
21 out << " Non-singleton SCC(s): " << sccsk << " (" << sccsk_dead << " dead)"
22 << std::endl;
23 out << " Singleton end component(s): " << ec1 << std::endl;
24 out << " Non-singleton end component(s): " << eck << std::endl;
25 out << " End-component transitions: " << ec_transitions << std::endl;
26 out << " Recursive calls: " << recursions << std::endl;
27 out << " End component computation: " << time << std::endl;
28}
29
30template <typename State, typename Action>
31bool EndComponentDecomposition<State, Action>::StateInfo::onstack() const
32{
33 return stackid < UNDEF;
34}
35
36template <typename State, typename Action>
37auto EndComponentDecomposition<State, Action>::StateInfo::get_status() const
38{
39 return explored ? (onstack() ? ONSTACK : CLOSED) : NEW;
40}
41
42template <typename State, typename Action>
43EndComponentDecomposition<State, Action>::ExpansionInfo::ExpansionInfo(
44 unsigned stck,
45 std::vector<Action> aops,
46 std::vector<std::vector<StateID>> successors)
47 : stck(stck)
48 , lstck(stck)
49 , nz_or_leaves_scc(false)
50 , aops(std::move(aops))
51 , successors(std::move(successors))
52{
53}
54
55template <typename State, typename Action>
56EndComponentDecomposition<State, Action>::ExpansionInfo::ExpansionInfo(
57 unsigned stck,
58 std::vector<Action> aops,
59 std::vector<std::vector<StateID>> successors,
60 MDPType& mdp)
61 : stck(stck)
62 , lstck(stck)
63 , nz_or_leaves_scc(mdp.get_action_cost(aops.back()) != 0_vt)
64 , aops(std::move(aops))
65 , successors(std::move(successors))
66{
67}
68
69template <typename State, typename Action>
70bool EndComponentDecomposition<State, Action>::ExpansionInfo::next_action(
71 std::nullptr_t)
72{
73 assert(aops.size() == successors.size());
74 aops.pop_back();
75 successors.pop_back();
76 nz_or_leaves_scc = false;
77 return !aops.empty();
78}
79
80template <typename State, typename Action>
81bool EndComponentDecomposition<State, Action>::ExpansionInfo::next_action(
82 MDPType& mdp)
83{
84 assert(aops.size() == successors.size());
85 aops.pop_back();
86 successors.pop_back();
87
88 if (!aops.empty()) {
89 nz_or_leaves_scc = mdp.get_action_cost(aops.back()) != 0_vt;
90 return true;
91 }
92
93 return false;
94}
95
96template <typename State, typename Action>
97bool EndComponentDecomposition<State, Action>::ExpansionInfo::next_successor()
98{
99 auto& succs = successors.back();
100 succs.pop_back();
101 return !succs.empty();
102}
103
104template <typename State, typename Action>
105StateID
106EndComponentDecomposition<State, Action>::ExpansionInfo::get_current_successor()
107{
108 return successors.back().back();
109}
110
111template <typename State, typename Action>
112Action&
113EndComponentDecomposition<State, Action>::ExpansionInfo::get_current_action()
114{
115 return aops.back();
116}
117
118template <typename State, typename Action>
119struct EndComponentDecomposition<State, Action>::StackInfo {
120 StateID stateid;
121
122 // SCC successors for ECD recursion
123 std::vector<Action> aops;
124 std::vector<std::vector<StateID>> successors;
125
126 explicit StackInfo(StateID sid)
127 : stateid(sid)
128 , successors(1)
129 {
130 }
131
132 template <size_t i>
133 friend auto& get(StackInfo& info)
134 {
135 if constexpr (i == 0) return info.stateid;
136 if constexpr (i == 1) return info.aops;
137 }
138
139 template <size_t i>
140 friend const auto& get(const StackInfo& info)
141 {
142 if constexpr (i == 0) return info.stateid;
143 if constexpr (i == 1) return info.aops;
144 }
145};
146
147template <typename State, typename Action>
148EndComponentDecomposition<State, Action>::EndComponentDecomposition(
149 bool expand_goals)
150 : expand_goals_(expand_goals)
151{
152}
153
154template <typename State, typename Action>
156 MDPType& mdp,
157 const EvaluatorType* pruning_function,
158 param_type<State> initial_state,
159 double max_time) -> std::unique_ptr<QSystem>
160{
161 utils::CountdownTimer timer(max_time);
162
163 stats_ = ECDStatistics();
164 auto sys = std::make_unique<QSystem>(mdp);
165
166 auto init_id = mdp.get_state_id(initial_state);
167
168 if (push(init_id, state_infos_[init_id], mdp, pruning_function)) {
169 find_and_decompose_sccs(*sys, 0, timer, mdp, pruning_function);
170 }
171
172 assert(stack_.empty());
173 assert(expansion_queue_.empty());
174 stats_.time.stop();
175
176 return sys;
177}
178
179template <typename State, typename Action>
181 std::ostream& out) const
182{
183 stats_.print(out);
184}
185
186template <typename State, typename Action>
187ECDStatistics EndComponentDecomposition<State, Action>::get_statistics() const
188{
189 return stats_;
190}
191
192template <typename State, typename Action>
193bool EndComponentDecomposition<State, Action>::push(
194 StateID state_id,
195 StateInfo& state_info,
196 MDPType& mdp,
197 const EvaluatorType* pruning_function)
198{
199 state_info.explored = 1;
200 State state = mdp.get_state(state_id);
201
202 const auto term = mdp.get_termination_info(state);
203
204 if (term.is_goal_state()) {
205 ++stats_.terminals;
206 ++stats_.goals;
207
208 if (!expand_goals_) {
209 return false;
210 }
211
212 state_info.expandable_goal = 1;
213 } else if (
214 pruning_function != nullptr &&
215 pruning_function->evaluate(state) == term.get_cost()) {
216 ++stats_.terminals;
217 return false;
218 }
219
220 std::vector<Action> aops;
221 mdp.generate_applicable_actions(state, aops);
222
223 if (aops.empty()) {
224 if (expand_goals_ && state_info.expandable_goal) {
225 state_info.expandable_goal = 0;
226 } else {
227 ++stats_.terminals;
228 }
229
230 return false;
231 }
232
233 std::vector<std::vector<StateID>> successors;
234 successors.reserve(aops.size());
235
236 unsigned non_loop_actions = 0;
237 for (unsigned i = 0; i < aops.size(); ++i) {
238 Distribution<StateID> transition;
239 mdp.generate_action_transitions(state, aops[i], transition);
240
241 std::vector<StateID> succ_ids;
242
243 for (StateID succ_id : transition.support()) {
244 if (succ_id != state_id) {
245 succ_ids.push_back(succ_id);
246 }
247 }
248
249 if (!succ_ids.empty()) {
250 successors.emplace_back(std::move(succ_ids));
251
252 if (i != non_loop_actions) {
253 aops[non_loop_actions] = aops[i];
254 }
255
256 ++non_loop_actions;
257 }
258 }
259
260 // only self-loops
261 if (non_loop_actions == 0) {
262 if (expand_goals_ && state_info.expandable_goal) {
263 state_info.expandable_goal = 0;
264 } else {
265 ++stats_.terminals;
266 ++stats_.selfloops;
267 }
268
269 return false;
270 }
271
272 aops.erase(aops.begin() + non_loop_actions, aops.end());
273
274 expansion_queue_.emplace_back(
275 stack_.size(),
276 std::move(aops),
277 std::move(successors),
278 mdp);
279
280 state_info.stackid = stack_.size();
281 stack_.emplace_back(state_id);
282
283 return true;
284}
285
286template <typename State, typename Action>
287bool EndComponentDecomposition<State, Action>::push(
288 StateID state_id,
289 StateInfo& info)
290{
291 assert(!info.explored);
292 assert(info.onstack());
293
294 info.explored = true;
295 StackInfo& scc_info = stack_[info.stackid];
296
297 if (scc_info.successors.empty()) {
298 info.stackid = StateInfo::UNDEF;
299 ++stats_.ec1;
300 return false;
301 }
302
303 const auto stack_size = stack_.size();
304 info.stackid = stack_size;
305
306 expansion_queue_.emplace_back(
307 stack_size,
308 std::move(scc_info.aops),
309 std::move(scc_info.successors));
310
311 stack_.emplace_back(state_id);
312
313 return true;
314}
315
316template <typename State, typename Action>
317void EndComponentDecomposition<State, Action>::find_and_decompose_sccs(
318 QSystem& sys,
319 const unsigned limit,
320 utils::CountdownTimer& timer,
321 auto&... mdp_and_h)
322{
323 if (expansion_queue_.size() <= limit) {
324 return;
325 }
326
327 ExpansionInfo* e = &expansion_queue_.back();
328 StackInfo* s = &stack_[e->stck];
329
330 for (;;) {
331 // DFS recursion
332 while (push_successor(*e, *s, timer, mdp_and_h...)) {
333 e = &expansion_queue_.back();
334 s = &stack_[e->stck];
335 }
336
337 // Iterative backtracking
338 do {
339 assert(s->successors.back().empty());
340 s->successors.pop_back();
341
342 assert(e->successors.empty() && e->aops.empty());
343
344 const bool recurse = e->recurse;
345 const unsigned int stck = e->stck;
346 const unsigned int lstck = e->lstck;
347
348 assert(stck >= lstck);
349
350 const bool scc_root = stck == lstck;
351
352 if (scc_root) {
353 scc_found<sizeof...(mdp_and_h) != 0>(sys, *e, *s, timer);
354 }
355
356 expansion_queue_.pop_back();
357
358 if (expansion_queue_.size() <= limit) {
359 return;
360 }
361
362 timer.throw_if_expired();
363
364 e = &expansion_queue_.back();
365 s = &stack_[e->stck];
366
367 auto& stk_successors = s->successors.back();
368
369 // Backtracked from successor.
370 if (scc_root) { // Child SCC
371 e->recurse = e->recurse || !stk_successors.empty();
372 e->nz_or_leaves_scc = true;
373 } else { // Same SCC
374 e->lstck = std::min(e->lstck, lstck);
375
376 e->recurse = e->recurse || recurse || e->nz_or_leaves_scc;
377 stk_successors.emplace_back(e->get_current_successor());
378 }
379
380 // If a successor exists stop backtracking
381 if (e->next_successor()) {
382 break;
383 }
384
385 // Finalize fully explored transition.
386 if (!e->nz_or_leaves_scc) {
387 assert(!stk_successors.empty());
388 s->aops.push_back(e->get_current_action());
389 s->successors.emplace_back();
390 } else {
391 stk_successors.clear();
392 }
393 } while (!e->next_action(select_opt<0>(mdp_and_h...)));
394 }
395}
396
397template <typename State, typename Action>
398bool EndComponentDecomposition<State, Action>::push_successor(
399 ExpansionInfo& e,
400 StackInfo& s,
401 utils::CountdownTimer& timer,
402 auto&... mdp_and_h)
403{
404 do {
405 std::vector<StateID>& s_succs = s.successors.back();
406
407 do {
408 timer.throw_if_expired();
409
410 const StateID succ_id = e.get_current_successor();
411 StateInfo& succ_info = state_infos_[succ_id];
412
413 switch (succ_info.get_status()) {
414 case StateInfo::NEW:
415 if (push(succ_id, succ_info, mdp_and_h...)) {
416 return true;
417 }
418
419 [[fallthrough]];
420
421 case StateInfo::CLOSED: // Child SCC
422 e.recurse = e.recurse || !s_succs.empty();
423 e.nz_or_leaves_scc = true;
424 break;
425
426 case StateInfo::ONSTACK: // Same SCC
427 e.lstck = std::min(e.lstck, succ_info.stackid);
428
429 e.recurse = e.recurse || e.nz_or_leaves_scc;
430 s_succs.emplace_back(succ_id);
431 }
432 } while (e.next_successor());
433
434 // Finalize fully explored transition.
435 if (!e.nz_or_leaves_scc) {
436 assert(!s_succs.empty());
437 s.successors.emplace_back();
438 s.aops.push_back(e.get_current_action());
439 } else {
440 s_succs.clear();
441 }
442 } while (e.next_action(select_opt<0>(mdp_and_h...)));
443
444 return false;
445}
446
447template <typename State, typename Action>
448template <bool RootIteration>
449void EndComponentDecomposition<State, Action>::scc_found(
450 QSystem& sys,
451 ExpansionInfo& e,
452 StackInfo& s,
453 utils::CountdownTimer& timer)
454{
455 auto scc = stack_ | std::views::drop(e.stck);
456
457 if (scc.size()) {
458 assert(s.aops.empty());
459 const StateID scc_repr_id = s.stateid;
460 StateInfo& info = state_infos_[scc_repr_id];
461 info.stackid = StateInfo::UNDEF;
462
463 stack_.pop_back();
464
465 // Update stats
466 ++stats_.ec1;
467
468 if constexpr (RootIteration) {
469 ++stats_.sccs1;
470 }
471 } else {
472 if (expand_goals_) {
473 for (auto& stk_info : scc) {
474 assert(stk_info.successors.size() == stk_info.aops.size());
475 StateInfo& info = state_infos_[stk_info.stateid];
476 if (info.expandable_goal) {
477 stk_info.successors.clear();
478 stk_info.aops.clear();
479 e.recurse = true;
480 }
481 }
482 }
483
484 if (e.recurse) {
485 ++stats_.recursions;
486
487 if constexpr (RootIteration) {
488 ++stats_.sccsk;
489 }
490
491 for (const auto& stk_info : scc) {
492 assert(stk_info.successors.size() == stk_info.aops.size());
493 state_infos_[stk_info.stateid].explored = 0;
494 }
495
496 decompose(sys, e.stck, timer);
497 } else {
498 unsigned transitions = 0;
499
500 for (const auto& stk_info : scc) {
501 assert(stk_info.successors.size() == stk_info.aops.size());
502 StateInfo& info = state_infos_[stk_info.stateid];
503 info.stackid = StateInfo::UNDEF;
504
505 transitions += stk_info.aops.size();
506 }
507
508 sys.build_new_quotient(scc, s);
509 stack_.erase(scc.begin(), scc.end());
510
511 // Update stats
512 ++stats_.eck;
513 stats_.ec_transitions += transitions;
514
515 if constexpr (RootIteration) {
516 ++stats_.sccsk;
517 }
518 }
519 }
520
521 assert(stack_.size() == e.stck);
522}
523
524template <typename State, typename Action>
525void EndComponentDecomposition<State, Action>::decompose(
526 QSystem& sys,
527 unsigned start,
528 utils::CountdownTimer& timer)
529{
530 const unsigned limit = expansion_queue_.size();
531
532 for (unsigned i = start; i < stack_.size(); ++i) {
533 const StateID id = stack_[i].stateid;
534 StateInfo& state_info = state_infos_[id];
535
536 if (!state_info.explored && push(id, state_info)) {
537 // Recursively run decomposition
538 find_and_decompose_sccs(sys, limit, timer);
539 }
540 }
541
542 stack_.erase(stack_.begin() + start, stack_.end());
543
544 assert(expansion_queue_.size() == limit);
545}
546
547} // namespace probfd::preprocessing
A builder class that implements end component decomposition.
Definition end_component_decomposition.h:86
This namespace contains preprocessing algorithms for SSPs.
Definition end_component_decomposition.h:24
typename std::conditional_t< is_cheap_to_copy_v< T >, T, const T & > param_type
Alias template defining the best way to pass a parameter of a given type.
Definition type_traits.h:25
STL namespace.
Contains printable statistics for the end component decomposition.
Definition end_component_decomposition.h:29