1#ifndef GUARD_INCLUDE_PROBFD_PREPROCESSING_QUALITATIVE_REACHABILITY_ANALYSIS_H
3 "This file should only be included from qualitative_reachability_analysis.h"
6#include "probfd/quotients/quotient_system.h"
8#include "probfd/storage/per_state_storage.h"
10#include "probfd/utils/iterators.h"
12#include "probfd/evaluator.h"
13#include "probfd/mdp.h"
14#include "probfd/type_traits.h"
16#include "downward/utils/countdown_timer.h"
17#include "downward/utils/timer.h"
29inline void QRStatistics::print(std::ostream& out)
const
31 out <<
" Terminal states: " << terminals <<
" (" << goals
32 <<
" goal states, " << selfloops <<
" self loop states)" << std::endl;
33 out <<
" Singleton SCC(s): " << sccs1 <<
" (" << sccs1_dead <<
" dead)"
35 out <<
" Non-singleton SCC(s): " << sccsk <<
" (" << sccsk_dead <<
" dead)"
37 out <<
" Qualitative reachability analysis: " << time << std::endl;
38 out <<
" " << ones <<
" state(s) can reach the goal with certainty"
44inline auto StateInfo::get_status()
const
46 return explored ? (stackid < UNDEF ? ONSTACK : CLOSED) : NEW;
49inline StackInfo::StackInfo(StateID sid)
56template <
typename State,
typename Action>
57QualitativeReachabilityAnalysis<State, Action>::ExpansionInfo::ExpansionInfo(
59 StackInfo& stack_info,
60 StateInfo& state_info,
63 , stack_info(stack_info)
64 , state_info(state_info)
70template <
typename State,
typename Action>
71bool QualitativeReachabilityAnalysis<State, Action>::ExpansionInfo::next_action(
75 exits_only_solvable =
true;
76 transitions_in_scc =
false;
82 return !aops.empty() && forward_non_self_loop(mdp, mdp.get_state(state_id));
85template <
typename State,
typename Action>
86bool QualitativeReachabilityAnalysis<State, Action>::ExpansionInfo::
87 forward_non_self_loop(MDPType& mdp,
const State& state)
90 mdp.generate_action_transitions(state, aops.back(), transition);
92 if (!transition.is_dirac(state_id)) {
93 successor = transition.begin();
99 }
while (!aops.empty());
104template <
typename State,
typename Action>
105bool QualitativeReachabilityAnalysis<State, Action>::ExpansionInfo::
108 if (++successor != transition.end()) {
112 if (transitions_in_scc) {
113 if (exits_only_solvable) {
115 ++stack_info.active_exit_transitions;
117 ++stack_info.active_transitions;
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;
130template <
typename State,
typename Action>
131StateID QualitativeReachabilityAnalysis<State, Action>::ExpansionInfo::
132 get_current_successor()
134 return successor->item;
137template <
typename State,
typename Action>
138QualitativeReachabilityAnalysis<State, Action>::QualitativeReachabilityAnalysis(
140 : expand_goals_(expand_goals)
144template <
typename State,
typename Action>
145void QualitativeReachabilityAnalysis<State, Action>::run_analysis(
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,
154 assert(expansion_queue_.empty());
156 utils::CountdownTimer timer(max_time);
158 const StateID init_id = mdp.get_state_id(source_state);
159 push_state(init_id, state_infos_[init_id]);
166 e = &expansion_queue_.back();
167 }
while (initialize(mdp, pruning_function, *e) &&
168 push_successor(mdp, *e, timer));
172 timer.throw_if_expired();
174 const unsigned stck = e->stck;
175 const unsigned lstck = e->lstck;
177 assert(stck >= lstck);
179 const bool backtrack_from_scc = stck == lstck;
181 if (backtrack_from_scc) {
182 scc_found(stck, dead_out, unsolvable_out, solvable_out, timer);
185 ExpansionInfo successor(std::move(*e));
186 expansion_queue_.pop_back();
188 if (expansion_queue_.empty())
return;
190 timer.throw_if_expired();
192 e = &expansion_queue_.back();
194 if (backtrack_from_scc) {
195 if (!successor.state_info.solvable)
196 e->exits_only_solvable =
false;
199 e->lstck = std::min(e->lstck, lstck);
200 e->transitions_in_scc =
true;
202 successor.stack_info.parents.emplace_back(
204 e->stack_info.transition_flags.size());
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));
213template <
typename State,
typename Action>
214bool QualitativeReachabilityAnalysis<State, Action>::initialize(
216 const EvaluatorType* pruning_function,
217 ExpansionInfo& exp_info)
222 exp_info.state_info.explored = 1;
224 const StateID state_id = exp_info.state_id;
225 State state = mdp.get_state(state_id);
227 const TerminationInfo term = mdp.get_termination_info(state);
229 if (term.is_goal_state()) {
232 exp_info.state_info.dead = 0;
233 ++exp_info.stack_info.active_exit_transitions;
234 ++exp_info.stack_info.active_transitions;
236 if (!expand_goals_) {
241 pruning_function !=
nullptr &&
242 pruning_function->evaluate(state) == term.get_cost()) {
247 mdp.generate_applicable_actions(state, exp_info.aops);
249 if (exp_info.aops.empty()) {
254 return exp_info.forward_non_self_loop(mdp, state);
257template <
typename State,
typename Action>
258void QualitativeReachabilityAnalysis<State, Action>::push_state(
260 StateInfo& state_info)
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);
268template <
typename State,
typename Action>
269bool QualitativeReachabilityAnalysis<State, Action>::push_successor(
271 ExpansionInfo& exp_info,
272 utils::CountdownTimer& timer)
275 timer.throw_if_expired();
277 const StateID succ_id = exp_info.get_current_successor();
278 StateInfo& succ_info = state_infos_[succ_id];
280 switch (succ_info.get_status()) {
281 case StateInfo::NEW: {
282 push_state(succ_id, succ_info);
286 case StateInfo::CLOSED:
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;
294 case StateInfo::ONSTACK:
295 unsigned succ_stack_id = succ_info.stackid;
296 exp_info.lstck = std::min(exp_info.lstck, succ_stack_id);
298 exp_info.transitions_in_scc =
true;
300 auto& parents = stack_[succ_stack_id].parents;
301 parents.emplace_back(
303 exp_info.stack_info.transition_flags.size());
305 }
while (exp_info.next_successor() || exp_info.next_action(mdp));
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)
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;
325 explicit Partition(std::size_t size)
326 : scc_index_to_local(size)
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);
334 solvable_beg = partition.begin();
335 solvable_exits_beg = partition.begin();
338 auto solvable_begin() {
return solvable_beg; }
339 auto solvable_end() {
return partition.end(); }
343 return std::ranges::subrange(solvable_begin(), solvable_end());
347 bool has_solvable()
const
349 return solvable_beg != partition.end();
352 void demote_unsolvable(
int s)
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);
361 void demote_exit_unsolvable(
int s)
363 auto local = scc_index_to_local[s];
365 scc_index_to_local[*solvable_exits_beg],
366 scc_index_to_local[s]);
368 scc_index_to_local[*solvable_beg],
369 scc_index_to_local[*solvable_exits_beg]);
371 std::swap(*solvable_exits_beg, *local);
372 std::swap(*solvable_beg, *solvable_exits_beg);
375 ++solvable_exits_beg;
378 void demote_exit_solvable(
int s)
380 auto local = scc_index_to_local[s];
382 scc_index_to_local[*solvable_exits_beg],
383 scc_index_to_local[s]);
384 std::swap(*solvable_exits_beg, *local);
386 ++solvable_exits_beg;
389 bool promote_solvable(
int s)
391 if (!is_unsolvable(s)) {
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);
404 void mark_non_exit_states_unsolvable()
406 solvable_beg = solvable_exits_beg;
409 bool is_unsolvable(
int s)
411 return scc_index_to_local[s] < solvable_beg;
415 using namespace std::views;
417 auto scc = stack_ | std::views::drop(stack_idx);
419 const StateInfo& st_info = state_infos_[std::ranges::begin(scc)->stateid];
422 for (
const StateID state_id : scc | transform(&StackInfo::stateid)) {
423 StateInfo& info = state_infos_[state_id];
424 info.stackid = StateInfo::UNDEF;
426 *dead_out = state_id;
427 *unsolvable_out = state_id;
430 stack_.erase(scc.begin(), scc.end());
439 Partition partition(scc.size());
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;
448 info.active_transitions != 0 || info.active_exit_transitions == 0);
451 for (
auto& parent_info : info.parents) {
452 parent_info.parent_idx -= stack_idx;
455 if (info.active_exit_transitions == 0) {
456 if (info.active_transitions > 0) {
457 partition.demote_exit_solvable(i);
459 partition.demote_exit_unsolvable(i);
464 if (partition.has_solvable()) {
467 timer.throw_if_expired();
471 auto unsolv_it = partition.solvable_begin();
473 partition.mark_non_exit_states_unsolvable();
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];
480 if (pinfo.transition_flags[tr_idx].is_active) {
481 partition.promote_solvable(parent_idx);
487 if (unsolv_it == partition.solvable_begin())
break;
492 timer.throw_if_expired();
494 StackInfo& scc_elem = scc[*unsolv_it];
497 assert(partition.is_unsolvable(*unsolv_it));
499 *unsolvable_out = scc_elem.stateid;
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];
506 !transition_flags.is_active_exiting ||
507 transition_flags.is_active);
509 if (partition.is_unsolvable(parent_idx))
continue;
511 if (transition_flags.is_active_exiting) {
512 transition_flags.is_active_exiting =
false;
513 transition_flags.is_active =
false;
515 --pinfo.active_transitions;
516 --pinfo.active_exit_transitions;
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);
523 }
else if (transition_flags.is_active) {
524 transition_flags.is_active =
false;
526 --pinfo.active_transitions;
528 if (pinfo.active_transitions == 0) {
529 partition.demote_unsolvable(parent_idx);
533 }
while (++unsolv_it != partition.solvable_begin());
537 auto solvable = partition.solvable();
538 stats_.ones += solvable.size();
541 for (
int scc_idx : solvable) {
542 StackInfo& stkinfo = scc[scc_idx];
543 const StateID sid = stkinfo.stateid;
544 state_infos_[sid].solvable =
true;
548 stack_.erase(scc.begin(), scc.end());
This namespace contains preprocessing algorithms for SSPs.
Definition end_component_decomposition.h:24