suiron/
solution_node.rs

1//! A SolutionNode is a node in a proof tree.
2//!
3//! It contains references to the [goal](../goal/enum.Goal.html) to be
4//! solved, the [knowledge base](../suiron/knowledge_base/index.html),
5//! the [SubstitutionSet](../substitution_set/index.html), and other
6//! relevant data.
7//!
8//! The function
9//! [next_solution()](../solution_node/fn.next_solution.html),
10//! accepts a solution node as its argument, and initiates the search
11//! for a solution. When a solution is found, the search stops.
12//!
13//! Each solution node preserves its state. Calling next_solution()
14//! again will continue the search for alternative solutions.
15//!
16// Cleve Lendon 2023
17
18use std::fmt;
19use std::rc::Rc;
20use std::cell::RefCell;
21use std::time::Instant;
22
23use crate::*;
24
25use super::goal::Goal;
26use super::substitution_set::*;
27use super::knowledge_base::*;
28
29/// Represents a node in a proof tree.
30///
31/// A solution node holds the goal to be resolved, various parameters
32/// concerning that goal, and a reference to the substitution set,
33/// which represents the state of the search so far.
34///
35#[derive(Debug, Clone)]
36pub struct SolutionNode<'a> {
37
38    /// The goal which this solution node seeks to resolve.
39    pub goal: Rc<Goal>,
40    /// Reference to the Knowledge Base.
41    pub kb: &'a KnowledgeBase,
42
43    /// Reference to the parent node in the proof tree.
44    pub parent_node: Option<Rc<RefCell<SolutionNode<'a>>>>,
45    /// Substitution Set - holds the complete or partial solution.
46    pub ss: Rc<SubstitutionSet<'a>>,
47    /// Flag used by the Cut operator (!) to prevent backtracking.
48    pub no_backtracking: bool,
49
50    // For Complex Solution Nodes.
51    /// Refers to the solution node of a rule's body. (For Complex goals.)
52    pub child: Option<Rc<RefCell<SolutionNode<'a>>>>,
53    /// The index of a fact or rule. (For Complex goals.)
54    pub rule_index: usize,
55    /// The number of facts and rules for the goal above. (For Complex goals.)
56    pub number_facts_rules: usize,
57
58    // For And/Or Solution Nodes.
59    /// Head solution node.
60    pub head_sn: Option<Rc<RefCell<SolutionNode<'a>>>>,
61    /// Tail solution node. (For And/Or goals.)
62    pub tail_sn: Option<Rc<RefCell<SolutionNode<'a>>>>,
63    /// Tail of And/Or operator. (For And/Or goals.)
64    pub operator_tail: Option<Operator>,
65
66    /// Flag for built-in predicates, which have only 1 solution.
67    pub more_solutions: bool,
68
69} // SolutionNode
70
71impl<'a> SolutionNode<'a> {
72
73    /// Creates a new SolutionNode struct, with default values.
74    ///
75    /// The parent_node is set to None, and the solution (ss)
76    /// is initialized to an empty substitution set.
77    ///
78    /// # Usage
79    /// ```
80    /// use std::rc::Rc;
81    /// use suiron::*;
82    ///
83    /// let goal = parse_query("test($X)").unwrap();
84    /// let kb   = test_kb();
85    /// let node = SolutionNode::new(Rc::new(goal), &kb);
86    /// ```
87    #[inline]
88    pub fn new(goal: Rc<Goal>, kb: &'a KnowledgeBase) -> Self {
89        SolutionNode {
90            goal, kb,
91            parent_node: None,
92            ss: empty_ss!(),
93            no_backtracking: false,
94            child: None,
95            rule_index: 0,
96            number_facts_rules: 0,
97            head_sn: None,
98            tail_sn: None,
99            operator_tail: None,
100            more_solutions: true,
101        }
102    } // new()
103
104    /// Sets the no_backtracking flag to true.
105    ///
106    /// The Cut operator (!) calls this method to disable backtracking
107    /// on the current node and all of its ancestors.
108    ///
109    /// # Note
110    /// In order to avoid weeks of whack-a-mole with compiler errors,
111    /// this method was implemented with 'unsafe' code.
112    ///
113    /// # Usage
114    /// ```
115    /// use std::rc::Rc;
116    /// use suiron::*;
117    ///
118    /// let kb = test_kb();
119    /// let query = parse_query("test").unwrap();
120    /// let solution_node = make_base_node(Rc::new(query), &kb);
121    ///
122    /// solution_node.borrow_mut().set_no_backtracking();
123    /// ```
124    pub fn set_no_backtracking(&mut self) {
125
126        self.no_backtracking = true;
127        let mut option_parent = &self.parent_node;
128        loop {
129            match option_parent {
130                None => { return; },
131                Some(pn) => {
132                    let raw_ptr = pn.as_ptr();
133                    unsafe {
134                        // Set no_backtracking on parent.
135                        (*raw_ptr).no_backtracking = true;
136                        // If there is a head solution node, set
137                        // the no_backtracking flag there also.
138                        if let Some(head_node) = &(*raw_ptr).head_sn {
139                            let raw_ptr2 = head_node.as_ptr();
140                            (*raw_ptr2).no_backtracking = true;
141                        }
142                        // Get the next parent.
143                        option_parent = &(*raw_ptr).parent_node;
144                    }
145                },
146            }
147        } // loop
148
149    } // set_no_backtracking()
150
151} // impl SolutionNode
152
153/// Gets the goal from a reference to a solution node.
154fn get_goal(sn: &Rc<RefCell<SolutionNode>>) -> Rc<Goal> {
155    let g = &sn.borrow().goal;
156    Rc::clone(g)
157}
158
159/// Gets the no_backtracking flag from a reference to a solution node.
160fn no_backtracking(sn: &Rc<RefCell<SolutionNode>>) -> bool {
161    sn.borrow().no_backtracking
162}
163
164/// Finds the first and next solutions of the given solution node.
165///
166/// This method fetches facts and rules from the knowledge base,
167/// and attempts to unify (match) each fact or rule head with the goal.
168/// When unification succeeds, if the rule has no body (ie. it is a fact),
169/// the method returns the updated substitution set.<br>
170///
171/// If there is a body, the method gets its solution node (child node)
172/// and attempts to solve that.<br>
173///
174/// If a fact or rule fails to unify, the method will fetch the next
175/// fact/rule until the relevant predicate in the knowledge base has
176/// been exhausted. In such a case, the method returns None to indicate
177/// failure.
178///
179/// # Usage
180/// ```
181/// use std::rc::Rc;
182/// use std::cell::RefCell;
183/// use suiron::*;
184///
185/// let kb = test_kb();
186///
187/// // Whom does Leonard love?
188/// // Make a query and a solution node.
189/// let query = parse_query("loves(Leonard, $Whom)").unwrap();
190/// let q = Rc::new(query);
191/// let solution_node = make_base_node(Rc::clone(&q), &kb);
192///
193/// // Get a solution.
194/// match next_solution(solution_node) {
195///     Some(ss) => {
196///         let result = q.replace_variables(&ss);
197///         println!("{}", result);
198///     },
199///     None => { println!("No."); },
200/// }
201/// // Prints: loves(Leonard, Penny)
202/// ```
203pub fn next_solution<'a>(sn: Rc<RefCell<SolutionNode<'a>>>)
204                         -> Option<Rc<SubstitutionSet<'a>>> {
205
206    if no_backtracking(&sn) { return None; }
207    let goal = get_goal(&sn);
208
209    match &*goal {
210
211        Goal::OperatorGoal(op) => {
212
213            match op {
214
215                Operator::And(_) => {
216                    return next_solution_and(sn);
217                },
218                Operator::Or(_) => {
219                    return next_solution_or(sn);
220                },
221
222                Operator::Time(_) => {
223
224                    let mut sn_ref = sn.borrow_mut();
225                    if !sn_ref.more_solutions { return None; };
226                    sn_ref.more_solutions = false;
227
228                    match &sn_ref.head_sn {
229                        Some(head_sn) => {
230                            let now = Instant::now();
231                            let solution = next_solution(Rc::clone(&head_sn));
232                            print_elapsed(now);
233                            return solution;
234                        },
235                        None => { panic!("next_solution() - \
236                                  Missing solution node. Should not happen."); },
237                    } // match
238
239                }, // Time
240
241                Operator::Not(_) => {
242
243                    let mut sn_ref = sn.borrow_mut();
244                    if !sn_ref.more_solutions { return None; };
245
246                    match &sn_ref.head_sn {
247                        Some(head_sn) => {
248                            let solution = next_solution(Rc::clone(&head_sn));
249                            match solution {
250                                Some(_) => return None,
251                                None => {
252                                    sn_ref.more_solutions = false;
253                                    return Some(Rc::clone(&sn_ref.ss));
254                                },
255                            }
256                        },
257                        None => { panic!("next_solution() - \
258                                  Missing solution node. Should not happen."); },
259                    } // match
260
261                }, // Not
262
263            } // match op
264
265        }, // Goal::OperatorGoal(op)
266
267        Goal::ComplexGoal(cmplx) => {
268
269            let mut sn_ref = sn.borrow_mut();
270
271            // Check for a child solution.
272            match &sn_ref.child {
273                None => {},
274                Some(child_sn) => {
275                    let solution = next_solution(Rc::clone(&child_sn));
276                    if solution.is_some() { return solution; }
277                },
278            }
279
280            sn_ref.child = None;
281            loop {
282
283                if sn_ref.rule_index >= sn_ref.number_facts_rules { return None; }
284
285                // The fallback_id saves the logic variable ID (LOGIC_VAR_ID),
286                // in case the next rule fails. Restoring this id will keep
287                // the length of the substitution set as short as possible.
288                let fallback_id = get_var_id();
289
290                let pred_name = sn_ref.goal.key();
291                let rule = get_rule(sn_ref.kb, &pred_name, sn_ref.rule_index);
292                sn_ref.rule_index += 1;
293
294                let head = rule.get_head();
295                let solution = head.unify(&cmplx, &sn_ref.ss);
296
297                match solution {
298                    None => { set_var_id(fallback_id); },  // Restore fallback ID.
299                    Some(ss) => {
300                        let body = rule.get_body();
301                        if body == Goal::Nil { return Some(ss); }
302                        let child_sn = make_solution_node(Rc::new(body),
303                                                          sn_ref.kb, ss,
304                                                          Rc::clone(&sn));
305                        sn_ref.child = Some(Rc::clone(&child_sn));
306                        let child_solution = next_solution(child_sn);
307                        if child_solution.is_some() { return child_solution; }
308                    },
309                } // match
310            }
311        },
312
313        Goal::BuiltInGoal(built_in_predicate) => {
314            return next_solution_bip(sn, built_in_predicate.clone());
315        },
316
317        _ => panic!("next_solution() - Implement this."),
318
319    } // match self
320
321} // next_solution()
322
323
324/// A utility for printing elapsed time.
325/// # Usage
326/// ```
327/// use std::rc::Rc;
328/// use std::time::Instant;
329/// use suiron::*;
330///
331/// let now = Instant::now();  // Mark start time.
332///
333/// // Do some stuff.
334/// let kb = test_kb();
335/// let query = parse_query("loves($Who, $Whom)").unwrap();
336/// let solution_node = make_base_node(Rc::new(query), &kb);
337/// next_solution(solution_node);
338///
339/// print_elapsed(now);  // Output elapsed time.
340/// ```
341pub fn print_elapsed(time: Instant) {
342    let elapsed = time.elapsed();
343    let seconds = elapsed.as_secs();
344    let micro = elapsed.subsec_nanos() / 1000;
345    if seconds == 1 {
346        print!("{} second {} microseconds ", seconds, micro);
347    }
348    else {
349        print!("{} seconds {} microseconds ", seconds, micro);
350    }
351} // print_elapsed()
352
353
354/// Displays a summary of a solution node for debugging purposes.<br>
355/// KB, the substitution set (ss) and tail_sn are excluded. For example:
356/// <pre>
357/// ----- Solution Node -----
358/// 	goal: grandfather($X, $Y)
359/// 	parent_node: None
360/// 	no_backtracking: false
361/// 	rule_index: 0
362/// 	number_facts_rules: 2
363/// 	head_sn: None
364/// 	operator_tail: None
365/// -------------------------
366/// </pre>
367
368impl fmt::Display for SolutionNode<'_> {
369    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
370        let mut out = "----- Solution Node -----\n".to_string();
371        out += &format!("\tgoal: {}\n", self.goal);
372        match &self.parent_node {
373            Some(parent) => {
374               let parent = parent.borrow();
375               out += &format!("\tparent_node (goal only): {}\n", parent.goal);
376            },
377            None => { out += "\tparent_node: None\n"},
378        }
379        out += &format!("\tno_backtracking: {}\n", self.no_backtracking);
380        out += &format!("\trule_index: {}\n", self.rule_index);
381        out += &format!("\tnumber_facts_rules: {}\n", self.number_facts_rules);
382        match &self.head_sn {
383            Some(head_sn) => {
384               let h = head_sn.borrow();
385               out += &format!("\thead_sn (goal only): {}\n", h.goal);
386            },
387            None => { out += "\thead_sn: None\n"},
388        }
389        match &self.operator_tail {
390            Some(operator_tail) => {
391               out += &format!("\toperator_tail: {}\n", operator_tail);
392            },
393            None => { out += "\toperator_tail: None\n"},
394        }
395        out += "-------------------------";
396        write!(f, "{}", out)
397    } // fmt
398} // fmt::Display
399
400
401#[cfg(test)]
402mod test {
403
404    use crate::*;
405    use std::rc::Rc;
406    use serial_test::serial;
407
408    // Test the set_no_backtracking() function.
409    // Some rules:
410    //    get_value($X) :- $X = 1.
411    //    get_value($X) :- $X = 2.
412    //    test1($X) :- get_value($X), $X == 2.    // one solution
413    //    test2($X) :- get_value($X), !, $X == 2. // no solutions
414    #[test]
415    #[serial]
416    fn test_set_no_backtracking() {
417
418        start_query();
419        let mut kb = KnowledgeBase::new();
420        let rule1 = parse_rule("get_value($X) :- $X = 1.").unwrap();
421        let rule2 = parse_rule("get_value($X) :- $X = 2.").unwrap();
422        let rule3 = parse_rule("test1($X) :- get_value($X), $X == 2.").unwrap();
423        let rule4 = parse_rule("test2($X) :- get_value($X), !, $X == 2.").unwrap();
424        add_rules!(&mut kb, rule1, rule2, rule3, rule4);
425
426        // Make a query.
427        let query = parse_query("test1($X)").unwrap();
428        let sn = make_base_node(Rc::new(query), &kb);
429
430        let solution = next_solution(Rc::clone(&sn));
431        match solution {
432            Some(_ss) => {},
433            None => { panic!("There should be 1 solution."); },
434        }
435
436        // Second query.
437        let query = parse_query("test2($X)").unwrap();
438        let sn = make_base_node(Rc::new(query), &kb);
439
440        let solution = next_solution(Rc::clone(&sn));
441        match solution {
442            None => {},
443            Some(_ss) => { panic!("There should be no solutions."); },
444        }
445
446    }  // test_set_no_backtracking()
447
448    // The test knowledge base has two predicates named love/2.
449    // This test function makes a query about who loves whom, and
450    // a corresponding solution node. The method next_solution()
451    // is called three times to confirm that all valid solutions
452    // can be found.
453    #[test]
454    #[serial]
455    fn test_next_solution1() {
456
457        start_query();  // SUIRON_STOP_QUERY = false, LOGIC_VAR_ID = 0
458
459        let kb = test_kb();
460
461        // Make a solution node for love.
462        let query = parse_query("loves($Who, $Whom)").unwrap();
463        let q = Rc::new(query);
464        let sn = make_base_node(Rc::clone(&q), &kb);
465
466        // Who loves whom?
467        let ss = next_solution(Rc::clone(&sn)).unwrap();
468        let result = q.replace_variables(&ss);
469        let s = format!("{}", result);
470        assert_eq!("loves(Leonard, Penny)", s);
471
472        // Who loves whom?
473        let ss = next_solution(Rc::clone(&sn)).unwrap();
474        let result = q.replace_variables(&ss);
475        let s = format!("{}", result);
476        assert_eq!("loves(Penny, Leonard)", s);
477
478        // All solutions found?
479        match next_solution(sn) {
480            Some(_) => { panic!("The love predicate should be exhausted."); },
481            None => { }, // I'm all outta love.
482        }
483
484    } // test_next_solution1()
485
486    // The test knowledge base has two rules which define a grandfather.
487    //     grandfather($X, $Y) :- father($X, $Z), father($Z, $Y).
488    //     grandfather($X, $Y) :- father($X, $Z), mother($Z, $Y).
489    // This test function makes a query to find grandfathers, and
490    // a corresponding solution node. The method next_solution() is
491    // called twice to confirm that all valid solutions can be found.
492
493    #[test]
494    #[serial]
495    fn test_next_solution2() {
496
497        start_query();  // SUIRON_STOP_QUERY = false, LOGIC_VAR_ID = 0
498        let kb = test_kb();
499
500        // Make a solution node to find grandfathers.
501        let query = parse_query("grandfather($Who, $Whom)").unwrap();
502        let q = Rc::new(query);
503        let sn = make_base_node(Rc::clone(&q), &kb);
504
505        match next_solution(Rc::clone(&sn)) {
506            Some(ss) => {
507                let s1 = format!("{}", q.replace_variables(&ss));
508                assert_eq!("grandfather(Alfred, Aethelstan)", s1);
509            },
510            None => { panic!("Cannot find grandfather."); },
511        }
512
513        match next_solution(Rc::clone(&sn)) {
514            Some(_) => { panic!("Solutions should be exhausted."); },
515            None => { },
516        }
517
518    } // test_next_solution2()
519
520} // test