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