graphplan/
lib.rs

1use std::collections::{HashSet};
2use std::hash::Hash;
3use std::fmt::{Debug, Display};
4use log::{debug};
5
6#[macro_use] pub mod macros;
7pub mod proposition;
8pub mod action;
9pub mod plangraph;
10pub mod solver;
11mod layer;
12mod pairset;
13
14pub use crate::proposition::Proposition;
15pub use crate::action::{Action, ActionType};
16pub use crate::plangraph::{PlanGraph, Solution};
17pub use crate::solver::{GraphPlanSolver, SimpleSolver};
18
19
20/// Represents a planning domain that can generate a GraphPlan via
21/// `from_domain`. Serves as a helper object to prevent lifetime
22/// issues when auto generating things like maintenance actions
23pub struct Domain<'a,
24                  ActionId: Debug + Hash + Ord + Clone,
25                  PropositionId: Debug + Display + Hash + Ord + Clone> {
26    initial_props: HashSet<&'a Proposition<PropositionId>>,
27    goals: HashSet<&'a Proposition<PropositionId>>,
28    actions: HashSet<Action<'a, ActionId, PropositionId>>
29}
30
31pub struct GraphPlan<'a,
32                     ActionId: Debug + Hash + Ord + Clone,
33                     PropositionId: Debug + Display + Hash + Ord + Clone> {
34    plangraph: PlanGraph<'a, ActionId, PropositionId>,
35}
36
37impl<'a,
38     ActionId: Debug + Hash + Ord + Clone,
39     PropositionId: Debug + Display + Hash + Ord + Clone> GraphPlan<'a, ActionId, PropositionId> {
40
41    /// Returns a new GraphPlan. Note: you probably want to use
42    /// `from_domain` instead.
43    pub fn new(initial_props: HashSet<&'a Proposition<PropositionId>>,
44               goals: HashSet<&'a Proposition<PropositionId>>,
45               actions: HashSet<&'a Action<'a, ActionId, PropositionId>>)
46               -> GraphPlan<'a, ActionId, PropositionId> {
47        let plangraph = PlanGraph::new(
48            initial_props,
49            goals,
50            actions,
51        );
52        GraphPlan { plangraph }
53    }
54
55    pub fn from_domain(domain: &'a Domain<'a, ActionId, PropositionId>)
56               -> GraphPlan<'a, ActionId, PropositionId> {
57        let plangraph = PlanGraph::new(
58            domain.initial_props.clone(),
59            domain.goals.clone(),
60            domain.actions.iter().collect(),
61        );
62        GraphPlan { plangraph }
63    }
64
65    /// Returns a domain with all maintenance actions.automatically
66    /// created. This is needed to avoid lifetime issues with PlanGraph
67    pub fn create_domain(initial_props: HashSet<&'a Proposition<PropositionId>>,
68                         goals: HashSet<&'a Proposition<PropositionId>>,
69                         actions: HashSet<&'a Action<'a, ActionId, PropositionId>>)
70                         -> Domain<'a, ActionId, PropositionId> {
71        let mut all_actions = HashSet::new();
72
73        for p in &initial_props {
74            all_actions.insert(Action::new_maintenance(*p));
75        }
76
77        for a in actions {
78            all_actions.insert(a.to_owned());
79
80            for p in &a.reqs {
81                all_actions.insert(Action::new_maintenance(p));
82            }
83
84            for p in &a.effects {
85                all_actions.insert(Action::new_maintenance(p));
86            }
87        }
88
89        Domain {
90            initial_props,
91            goals,
92            actions: all_actions,
93        }
94    }
95
96    pub fn search<Solver>(&mut self) -> Option<Solution<'a, ActionId, PropositionId>>
97        where Solver: GraphPlanSolver<'a, ActionId, PropositionId> {
98
99        let mut tries = 0;
100        let mut solution = None;
101        let max_tries = self.plangraph.actions.len() + 1;
102
103        while tries < max_tries {
104            self.plangraph.extend();
105            // This doesn't provide early termination for _all_
106            // cases that won't yield a solution.
107            if self.plangraph.has_leveled_off() {
108                break;
109            }
110
111            if !self.plangraph.has_possible_solution() {
112                debug!("No solution exists at depth {}", self.plangraph.depth());
113                tries += 1;
114                continue
115            }
116
117            if let Some(result) = Solver::search(&self.plangraph) {
118                solution = Some(result);
119                break;
120            } else {
121                debug!("No solution found at depth {}", self.plangraph.depth());
122                tries += 1
123            }
124        };
125
126        solution
127    }
128
129    /// Takes a solution and filters out maintenance actions
130    pub fn format_plan(solution: Solution<ActionId, PropositionId>) -> Solution<ActionId, PropositionId> {
131        solution.iter()
132            .map(|s| s.iter()
133                 .filter(|i| match i.id {
134                     ActionType::Action(_) => true,
135                     ActionType::Maintenance(_) => false})
136                 .cloned()
137                 .collect())
138            .collect()
139    }
140}
141
142#[cfg(test)]
143mod integration_test {
144    use crate::GraphPlan;
145    use crate::proposition::Proposition;
146    use crate::action::Action;
147    use crate::solver::SimpleSolver;
148
149    #[test]
150    fn integration() {
151        let p1 = Proposition::from("tired");
152        let not_p1 = p1.negate();
153        let p2 = Proposition::from("dog needs to pee");
154        let not_p2 = p2.negate();
155
156        let a1 = Action::new(
157            "coffee",
158            hashset!{&p1},
159            hashset!{&not_p1}
160        );
161
162        let a2 = Action::new(
163            "walk dog",
164            hashset!{&p2, &not_p1},
165            hashset!{&not_p2},
166        );
167
168        let actions = hashset!{&a1, &a2};
169        let initial_props = hashset!{&p1, &p2};
170        let goals = hashset!{&not_p1, &not_p2};
171
172        let domain = GraphPlan::create_domain(
173            initial_props,
174            goals,
175            actions,
176        );
177
178        let mut pg = GraphPlan::<&str, &str>::from_domain(&domain);
179        assert!(pg.search::<SimpleSolver>() != None, "Solution should not be None");
180    }
181}