AI 24/25 Project Software
Documentation for the AI 24/25 course programming project software
Loading...
Searching...
No Matches
qualitative_reachability_analysis_impl.h
1#ifndef GUARD_INCLUDE_PROBFD_PREPROCESSING_QUALITATIVE_REACHABILITY_ANALYSIS_H
2#error \
3 "This file should only be included from qualitative_reachability_analysis.h"
4#endif
5
6#include "probfd/quotients/quotient_system.h"
7
8#include "probfd/storage/per_state_storage.h"
9
10#include "probfd/utils/iterators.h"
11
12#include "probfd/evaluator.h"
13#include "probfd/mdp.h"
14#include "probfd/type_traits.h"
15
16#include "downward/utils/countdown_timer.h"
17#include "downward/utils/timer.h"
18
19#include <cassert>
20#include <deque>
21#include <iostream>
22#include <iterator>
23#include <limits>
24#include <type_traits>
25#include <vector>
26
27namespace probfd::preprocessing {
28
29inline void QRStatistics::print(std::ostream& out) const
30{
31 out << " Terminal states: " << terminals << " (" << goals
32 << " goal states, " << selfloops << " self loop states)" << std::endl;
33 out << " Singleton SCC(s): " << sccs1 << " (" << sccs1_dead << " dead)"
34 << std::endl;
35 out << " Non-singleton SCC(s): " << sccsk << " (" << sccsk_dead << " dead)"
36 << std::endl;
37 out << " Qualitative reachability analysis: " << time << std::endl;
38 out << " " << ones << " state(s) can reach the goal with certainty"
39 << std::endl;
40}
41
42namespace internal {
43
44inline auto StateInfo::get_status() const
45{
46 return explored ? (stackid < UNDEF ? ONSTACK : CLOSED) : NEW;
47}
48
49inline StackInfo::StackInfo(StateID sid)
50 : stateid(sid)
51{
52}
53
54} // namespace internal
55
56template <typename State, typename Action>
57QualitativeReachabilityAnalysis<State, Action>::ExpansionInfo::ExpansionInfo(
58 StateID state_id,
59 StackInfo& stack_info,
60 StateInfo& state_info,
61 unsigned int stck)
62 : state_id(state_id)
63 , stack_info(stack_info)
64 , state_info(state_info)
65 , stck(stck)
66 , lstck(stck)
67{
68}
69
70template <typename State, typename Action>
71bool QualitativeReachabilityAnalysis<State, Action>::ExpansionInfo::next_action(
72 MDPType& mdp)
73{
74 // Reset transition flags
75 exits_only_solvable = true;
76 transitions_in_scc = false;
77 exits_scc = false;
78
79 aops.pop_back();
80 transition.clear();
81
82 return !aops.empty() && forward_non_self_loop(mdp, mdp.get_state(state_id));
83}
84
85template <typename State, typename Action>
86bool QualitativeReachabilityAnalysis<State, Action>::ExpansionInfo::
87 forward_non_self_loop(MDPType& mdp, const State& state)
88{
89 do {
90 mdp.generate_action_transitions(state, aops.back(), transition);
91
92 if (!transition.is_dirac(state_id)) {
93 successor = transition.begin();
94 return true;
95 }
96
97 aops.pop_back();
98 transition.clear();
99 } while (!aops.empty());
100
101 return false;
102}
103
104template <typename State, typename Action>
105bool QualitativeReachabilityAnalysis<State, Action>::ExpansionInfo::
106 next_successor()
107{
108 if (++successor != transition.end()) {
109 return true;
110 }
111
112 if (transitions_in_scc) {
113 if (exits_only_solvable) {
114 if (exits_scc) {
115 ++stack_info.active_exit_transitions;
116 }
117 ++stack_info.active_transitions;
118 }
119 stack_info.transition_flags.emplace_back(
120 exits_only_solvable && exits_scc,
121 static_cast<bool>(exits_only_solvable));
122 } else if (exits_only_solvable) {
123 ++stack_info.active_exit_transitions;
124 ++stack_info.active_transitions;
125 }
126
127 return false;
128}
129
130template <typename State, typename Action>
131StateID QualitativeReachabilityAnalysis<State, Action>::ExpansionInfo::
132 get_current_successor()
133{
134 return successor->item;
135}
136
137template <typename State, typename Action>
138QualitativeReachabilityAnalysis<State, Action>::QualitativeReachabilityAnalysis(
139 bool expand_goals)
140 : expand_goals_(expand_goals)
141{
142}
143
144template <typename State, typename Action>
145void QualitativeReachabilityAnalysis<State, Action>::run_analysis(
146 MDPType& mdp,
147 const EvaluatorType* pruning_function,
148 param_type<State> source_state,
149 std::output_iterator<StateID> auto dead_out,
150 std::output_iterator<StateID> auto unsolvable_out,
151 std::output_iterator<StateID> auto solvable_out,
152 double max_time)
153{
154 assert(expansion_queue_.empty());
155
156 utils::CountdownTimer timer(max_time);
157
158 const StateID init_id = mdp.get_state_id(source_state);
159 push_state(init_id, state_infos_[init_id]);
160
161 for (;;) {
162 ExpansionInfo* e;
163
164 // DFS recursion
165 do {
166 e = &expansion_queue_.back();
167 } while (initialize(mdp, pruning_function, *e) &&
168 push_successor(mdp, *e, timer));
169
170 // Repeated backtracking
171 do {
172 timer.throw_if_expired();
173
174 const unsigned stck = e->stck;
175 const unsigned lstck = e->lstck;
176
177 assert(stck >= lstck);
178
179 const bool backtrack_from_scc = stck == lstck;
180
181 if (backtrack_from_scc) {
182 scc_found(stck, dead_out, unsolvable_out, solvable_out, timer);
183 }
184
185 ExpansionInfo successor(std::move(*e));
186 expansion_queue_.pop_back();
187
188 if (expansion_queue_.empty()) return;
189
190 timer.throw_if_expired();
191
192 e = &expansion_queue_.back();
193
194 if (backtrack_from_scc) {
195 if (!successor.state_info.solvable)
196 e->exits_only_solvable = false;
197 e->exits_scc = true;
198 } else {
199 e->lstck = std::min(e->lstck, lstck);
200 e->transitions_in_scc = true;
201
202 successor.stack_info.parents.emplace_back(
203 e->stck,
204 e->stack_info.transition_flags.size());
205 }
206
207 if (!successor.state_info.dead) e->state_info.dead = false;
208 } while ((!e->next_successor() && !e->next_action(mdp)) ||
209 !push_successor(mdp, *e, timer));
210 }
211}
212
213template <typename State, typename Action>
214bool QualitativeReachabilityAnalysis<State, Action>::initialize(
215 MDPType& mdp,
216 const EvaluatorType* pruning_function,
217 ExpansionInfo& exp_info)
218{
219 // assert(!state_info.explored);
220 // assert(!state_info.solvable && state_info.dead);
221
222 exp_info.state_info.explored = 1;
223
224 const StateID state_id = exp_info.state_id;
225 State state = mdp.get_state(state_id);
226
227 const TerminationInfo term = mdp.get_termination_info(state);
228
229 if (term.is_goal_state()) {
230 ++stats_.goals;
231
232 exp_info.state_info.dead = 0;
233 ++exp_info.stack_info.active_exit_transitions;
234 ++exp_info.stack_info.active_transitions;
235
236 if (!expand_goals_) {
237 ++stats_.terminals;
238 return false;
239 }
240 } else if (
241 pruning_function != nullptr &&
242 pruning_function->evaluate(state) == term.get_cost()) {
243 ++stats_.terminals;
244 return false;
245 }
246
247 mdp.generate_applicable_actions(state, exp_info.aops);
248
249 if (exp_info.aops.empty()) {
250 ++stats_.terminals;
251 return false;
252 }
253
254 return exp_info.forward_non_self_loop(mdp, state);
255}
256
257template <typename State, typename Action>
258void QualitativeReachabilityAnalysis<State, Action>::push_state(
259 StateID state_id,
260 StateInfo& state_info)
261{
262 const std::size_t stack_size = stack_.size();
263 state_info.stackid = stack_size;
264 auto& stack_info = stack_.emplace_back(state_id);
265 expansion_queue_.emplace_back(state_id, stack_info, state_info, stack_size);
266}
267
268template <typename State, typename Action>
269bool QualitativeReachabilityAnalysis<State, Action>::push_successor(
270 MDPType& mdp,
271 ExpansionInfo& exp_info,
272 utils::CountdownTimer& timer)
273{
274 do {
275 timer.throw_if_expired();
276
277 const StateID succ_id = exp_info.get_current_successor();
278 StateInfo& succ_info = state_infos_[succ_id];
279
280 switch (succ_info.get_status()) {
281 case StateInfo::NEW: {
282 push_state(succ_id, succ_info);
283 return true;
284 }
285
286 case StateInfo::CLOSED: // Child SCC
287 exp_info.exits_only_solvable =
288 exp_info.exits_only_solvable && succ_info.solvable;
289 exp_info.exits_scc = true;
290 exp_info.state_info.dead =
291 exp_info.state_info.dead && succ_info.dead;
292 break;
293
294 case StateInfo::ONSTACK: // Same SCC
295 unsigned succ_stack_id = succ_info.stackid;
296 exp_info.lstck = std::min(exp_info.lstck, succ_stack_id);
297
298 exp_info.transitions_in_scc = true;
299
300 auto& parents = stack_[succ_stack_id].parents;
301 parents.emplace_back(
302 exp_info.stck,
303 exp_info.stack_info.transition_flags.size());
304 }
305 } while (exp_info.next_successor() || exp_info.next_action(mdp));
306
307 return false;
308}
309
310template <typename State, typename Action>
311void QualitativeReachabilityAnalysis<State, Action>::scc_found(
312 unsigned int stack_idx,
313 std::output_iterator<StateID> auto dead_out,
314 std::output_iterator<StateID> auto unsolvable_out,
315 std::output_iterator<StateID> auto solvable_out,
316 utils::CountdownTimer& timer)
317{
318 class Partition {
319 std::vector<std::vector<int>::iterator> scc_index_to_local;
320 std::vector<int> partition;
321 std::vector<int>::iterator solvable_beg;
322 std::vector<int>::iterator solvable_exits_beg;
323
324 public:
325 explicit Partition(std::size_t size)
326 : scc_index_to_local(size)
327 , partition(size, 0)
328 {
329 for (unsigned int i = 0; i != size; ++i) {
330 scc_index_to_local[i] = partition.begin() + i;
331 partition[i] = static_cast<int>(i);
332 }
333
334 solvable_beg = partition.begin();
335 solvable_exits_beg = partition.begin();
336 }
337
338 auto solvable_begin() { return solvable_beg; }
339 auto solvable_end() { return partition.end(); }
340
341 auto solvable()
342 {
343 return std::ranges::subrange(solvable_begin(), solvable_end());
344 }
345
346 [[nodiscard]]
347 bool has_solvable() const
348 {
349 return solvable_beg != partition.end();
350 }
351
352 void demote_unsolvable(int s)
353 {
354 auto local = scc_index_to_local[s];
355 std::swap(scc_index_to_local[*solvable_beg], scc_index_to_local[s]);
356 std::swap(*solvable_beg, *local);
357
358 ++solvable_beg;
359 }
360
361 void demote_exit_unsolvable(int s)
362 {
363 auto local = scc_index_to_local[s];
364 std::swap(
365 scc_index_to_local[*solvable_exits_beg],
366 scc_index_to_local[s]);
367 std::swap(
368 scc_index_to_local[*solvable_beg],
369 scc_index_to_local[*solvable_exits_beg]);
370
371 std::swap(*solvable_exits_beg, *local);
372 std::swap(*solvable_beg, *solvable_exits_beg);
373
374 ++solvable_beg;
375 ++solvable_exits_beg;
376 }
377
378 void demote_exit_solvable(int s)
379 {
380 auto local = scc_index_to_local[s];
381 std::swap(
382 scc_index_to_local[*solvable_exits_beg],
383 scc_index_to_local[s]);
384 std::swap(*solvable_exits_beg, *local);
385
386 ++solvable_exits_beg;
387 }
388
389 bool promote_solvable(int s)
390 {
391 if (!is_unsolvable(s)) {
392 return false;
393 }
394
395 --solvable_beg;
396
397 auto local = scc_index_to_local[s];
398 std::swap(scc_index_to_local[*solvable_beg], scc_index_to_local[s]);
399 std::swap(*solvable_beg, *local);
400
401 return true;
402 }
403
404 void mark_non_exit_states_unsolvable()
405 {
406 solvable_beg = solvable_exits_beg;
407 }
408
409 bool is_unsolvable(int s)
410 {
411 return scc_index_to_local[s] < solvable_beg;
412 }
413 };
414
415 using namespace std::views;
416
417 auto scc = stack_ | std::views::drop(stack_idx);
418
419 const StateInfo& st_info = state_infos_[std::ranges::begin(scc)->stateid];
420
421 if (st_info.dead) {
422 for (const StateID state_id : scc | transform(&StackInfo::stateid)) {
423 StateInfo& info = state_infos_[state_id];
424 info.stackid = StateInfo::UNDEF;
425 assert(info.dead);
426 *dead_out = state_id;
427 *unsolvable_out = state_id;
428 }
429
430 stack_.erase(scc.begin(), scc.end());
431 return;
432 }
433
434 // Compute the set of solvable states of this SCC.
435 // Start by partitioning states into inactive states, active exits and
436 // active non-exists.
437 // The partition is initialized optimistically, i.e., all states start out
438 // as active exits.
439 Partition partition(scc.size());
440
441 for (std::size_t i = 0; i != scc.size(); ++i) {
442 StackInfo& info = scc[i];
443 StateInfo& state_info = state_infos_[info.stateid];
444 state_info.stackid = StateInfo::UNDEF;
445 state_info.dead = false;
446
447 assert(
448 info.active_transitions != 0 || info.active_exit_transitions == 0);
449
450 // Transform to local indices
451 for (auto& parent_info : info.parents) {
452 parent_info.parent_idx -= stack_idx;
453 }
454
455 if (info.active_exit_transitions == 0) {
456 if (info.active_transitions > 0) {
457 partition.demote_exit_solvable(i);
458 } else {
459 partition.demote_exit_unsolvable(i);
460 }
461 }
462 }
463
464 if (partition.has_solvable()) {
465 // Compute the set of solvable states of this SCC.
466 for (;;) {
467 timer.throw_if_expired();
468
469 // Collect states that can currently reach an exit and mark other
470 // states unsolvable.
471 auto unsolv_it = partition.solvable_begin();
472
473 partition.mark_non_exit_states_unsolvable();
474
475 for (auto it = partition.solvable_end();
476 it != partition.solvable_begin();) {
477 for (const auto& [parent_idx, tr_idx] : scc[*--it].parents) {
478 StackInfo& pinfo = scc[parent_idx];
479
480 if (pinfo.transition_flags[tr_idx].is_active) {
481 partition.promote_solvable(parent_idx);
482 }
483 }
484 }
485
486 // No new unsolvable states -> stop.
487 if (unsolv_it == partition.solvable_begin()) break;
488
489 // Run fixpoint iteration starting with the new unsolvable states
490 // that could not reach an exit anymore.
491 do {
492 timer.throw_if_expired();
493
494 StackInfo& scc_elem = scc[*unsolv_it];
495
496 // The state was marked unsolvable.
497 assert(partition.is_unsolvable(*unsolv_it));
498
499 *unsolvable_out = scc_elem.stateid;
500
501 for (const auto& [parent_idx, tr_idx] : scc_elem.parents) {
502 StackInfo& pinfo = scc[parent_idx];
503 auto& transition_flags = pinfo.transition_flags[tr_idx];
504
505 assert(
506 !transition_flags.is_active_exiting ||
507 transition_flags.is_active);
508
509 if (partition.is_unsolvable(parent_idx)) continue;
510
511 if (transition_flags.is_active_exiting) {
512 transition_flags.is_active_exiting = false;
513 transition_flags.is_active = false;
514
515 --pinfo.active_transitions;
516 --pinfo.active_exit_transitions;
517
518 if (pinfo.active_transitions == 0) {
519 partition.demote_exit_unsolvable(parent_idx);
520 } else if (pinfo.active_exit_transitions == 0) {
521 partition.demote_exit_solvable(parent_idx);
522 }
523 } else if (transition_flags.is_active) {
524 transition_flags.is_active = false;
525
526 --pinfo.active_transitions;
527
528 if (pinfo.active_transitions == 0) {
529 partition.demote_unsolvable(parent_idx);
530 }
531 }
532 }
533 } while (++unsolv_it != partition.solvable_begin());
534 }
535 }
536
537 auto solvable = partition.solvable();
538 stats_.ones += solvable.size();
539
540 // Report the solvable states
541 for (int scc_idx : solvable) {
542 StackInfo& stkinfo = scc[scc_idx];
543 const StateID sid = stkinfo.stateid;
544 state_infos_[sid].solvable = true;
545 *solvable_out = sid;
546 }
547
548 stack_.erase(scc.begin(), scc.end());
549}
550
551} // namespace probfd::preprocessing
This namespace contains preprocessing algorithms for SSPs.
Definition end_component_decomposition.h:24