suiron/solutions.rs
1//! Functions which search for and format solutions.
2
3use std::env;
4use std::rc::Rc;
5use std::cell::RefCell;
6
7use super::goal::Goal;
8use super::time_out::*;
9use super::solution_node::*;
10use super::unifiable::Unifiable;
11
12const S_TIMEOUT: u64 = 1000; // milliseconds
13const NO_MORE: &str = "No more.";
14
15/// Finds one solution for the given solution node.
16///
17/// # Arguments
18/// * `sn` - reference to a [SolutionNode](../solution_node/struct.SolutionNode.html)
19/// # Return
20/// * `solution` - String
21/// # Usage
22/// ```
23/// use std::rc::Rc;
24/// use suiron::*;
25///
26/// let mut kb = test_kb();
27/// let query = parse_query("loves($Who, $Whom)").unwrap();
28/// let sn = make_base_node(Rc::new(query), &kb); // solution node
29///
30/// println!("{}", solve(Rc::clone(&sn)));
31/// // Prints: $Who = Leonard, $Whom = Penny
32/// println!("{}", solve(Rc::clone(&sn)));
33/// // Prints: $Who = Penny, $Whom = Leonard
34/// println!("{}", solve(Rc::clone(&sn)));
35/// // Prints: No.
36/// ```
37pub fn solve<'a>(sn: Rc<RefCell<SolutionNode<'a>>>) -> String {
38
39 let timer = start_query_timer(S_TIMEOUT);
40
41 let solution = next_solution(Rc::clone(&sn));
42 cancel_timer(timer);
43
44 if query_stopped() {
45 return format!("Query timed out after {} \
46 milliseconds.", S_TIMEOUT);
47 }
48
49 match solution {
50 Some(ss) => {
51 let query = sn.borrow().goal.clone();
52 let result = query.replace_variables(&ss);
53 return format_solution(&query, &result);
54 },
55 None => { return NO_MORE.to_string(); },
56 } // match solution
57
58} // solve()
59
60/// Finds all solutions for the given query.
61///
62/// # Arguments
63/// * `sn` - reference to a [SolutionNode](../solution_node/struct.SolutionNode.html)
64/// # Return
65/// * `solutions` - vector of Strings
66/// # Usage
67/// ```
68/// use std::rc::Rc;
69/// use suiron::*;
70///
71/// let mut kb = test_kb();
72/// let query = parse_query("loves($Who, $Whom)").unwrap();
73/// let sn = make_base_node(Rc::new(query), &kb); // solution node
74///
75/// let results = solve_all(Rc::clone(&sn));
76/// for result in results { println!("{}", result); }
77/// // Prints:
78/// // $Who = Leonard, $Whom = Penny
79/// // $Who = Penny, $Whom = Leonard
80/// // No.
81/// ```
82pub fn solve_all<'a>(sn: Rc<RefCell<SolutionNode<'a>>>) -> Vec<String> {
83
84 let mut results: Vec<String> = vec![];
85
86 let query = sn.borrow().goal.clone();
87 let timer = start_query_timer(S_TIMEOUT);
88
89 loop {
90
91 let solution = next_solution(Rc::clone(&sn));
92 if query_stopped() { break; }
93
94 match solution {
95 Some(ss) => {
96 let result = query.replace_variables(&ss);
97 let s = format_solution(&query, &result);
98 results.push(s);
99 },
100 None => { break; }
101 } // match solution
102
103 } // loop
104
105 cancel_timer(timer);
106 if query_stopped() {
107 let s = format!("Query timed out after {} milliseconds.", S_TIMEOUT);
108 results.push(s);
109 }
110
111 return results;
112
113} // solve_all()
114
115/// Formats the results of a query for display.
116///
117/// For example, if the query were `loves(Leonard, $Whom)`, and
118/// the result were `loves(Leonard, Penny)`, then the function
119/// would return: `$Whom = Penny`
120///
121/// This function iterates through the query's terms. For every
122/// logic variable in the query, it prints the variable's name
123/// and the corresponding term from the result.
124///
125/// # Arguments
126/// * query - [Goal](../goal/enum.Goal.html)
127/// * [Unifiable](../unifiable/enum.Unifiable.html) term
128/// # Return
129/// * formatted solution
130/// # Usage
131/// ```
132/// use std::rc::Rc;
133/// use suiron::*;
134///
135/// let kb = test_kb();
136/// let query = parse_query("loves(Leonard, $Whom)").unwrap();
137/// let q = Rc::new(query);
138/// let sn = make_base_node(Rc::clone(&q), &kb); // solution node
139///
140/// if let Some(ss) = next_solution(Rc::clone(&sn)) {
141/// let result = q.replace_variables(&ss);
142/// println!("{}", format_solution(&q, &result));
143/// }
144/// // Prints: $Whom = Penny
145/// ```
146pub fn format_solution(query: &Goal, result: &Unifiable) -> String {
147 let mut out = "".to_string();
148 // Deconstruct the query.
149 if let Goal::ComplexGoal(Unifiable::SComplex(q_terms)) = query {
150 // Deconstruct the result.
151 if let Unifiable::SComplex(r_terms) = result {
152 let mut first = true;
153 for i in 1..q_terms.len() {
154 // Scan for logic variables.
155 match &q_terms[i] {
156 Unifiable::LogicVar{id: _, name} => {
157 // Output logic variable name and result.
158 if first {
159 out += &format!("{} = {}", name, r_terms[i]);
160 first = false;
161 }
162 else {
163 out += &format!(", {} = {}", name, r_terms[i]);
164 }
165 },
166 _ => {},
167 } // match
168 } // for
169 } // if
170 } // if
171 return out;
172} // format_solution()
173
174
175/// Gets the environment variable RUST_MIN_STACK.
176///
177/// If RUST_MIN_STACK is not set, returns 0.
178///
179/// # Return
180/// * stack size (i32)
181pub fn get_stack_size() -> i32 {
182 let rust_min_stack = match env::var("RUST_MIN_STACK") {
183 Ok(val) => val,
184 Err(_e) => "0".to_string(),
185 };
186 match rust_min_stack.parse() {
187 Ok(i) => { return i; },
188 Err(_) => { return 0; },
189 }
190} // get_stack_size()
191
192
193#[cfg(test)]
194mod test {
195
196 use std::rc::Rc;
197 use serial_test::serial;
198
199 use crate::*;
200 use super::*;
201
202 #[test]
203 #[serial]
204 fn test_format_solution() {
205
206 let kb = test_kb();
207 let query = parse_query("loves($Who, $Whom)").unwrap();
208 let q = Rc::new(query.clone());
209 let sn = make_base_node(q, &kb); // solution node
210
211 let mut ss = next_solution(Rc::clone(&sn));
212
213 match ss {
214 Some(ss2) => {
215 let result = query.replace_variables(&ss2);
216 let result = format_solution(&query, &result);
217 assert_eq!("$Who = Leonard, $Whom = Penny", result);
218 ss = next_solution(Rc::clone(&sn));
219 },
220 None => {
221 panic!("Missing Solution 1.");
222 },
223 }
224
225 match ss {
226 Some(ss2) => {
227 let result = query.replace_variables(&ss2);
228 let result = format_solution(&query, &result);
229 assert_eq!("$Who = Penny, $Whom = Leonard", result);
230 ss = next_solution(Rc::clone(&sn));
231 },
232 None => {
233 panic!("Missing Solution 2.");
234 },
235 }
236
237 match ss {
238 Some(_) => { panic!("The solutions should be exhausted."); },
239 None => {},
240 }
241
242 } // test_format_solution()
243
244 #[test]
245 #[serial]
246 fn test_solve() {
247
248 clear_id();
249 let kb = test_kb();
250 let query = parse_query("loves(Leonard, $Whom)").unwrap();
251 let sn = make_base_node(Rc::new(query), &kb); // solution node
252 let solution = solve(Rc::clone(&sn));
253 assert_eq!("$Whom = Penny", solution);
254
255 } // test_solve()
256
257 #[test]
258 #[serial]
259 fn test_solve_all() {
260
261 clear_id();
262 let kb = test_kb();
263 let query = parse_query("loves($Who, $Whom)").unwrap();
264 let sn = make_base_node(Rc::new(query), &kb); // solution node
265 let results = solve_all(Rc::clone(&sn));
266
267 assert_eq!(results.len(), 2, "There should be 2 solutions.");
268 assert_eq!("$Who = Leonard, $Whom = Penny", results[0]);
269 assert_eq!("$Who = Penny, $Whom = Leonard", results[1]);
270
271 } // test_solve_all()
272
273 // NOTE: The default stack for Rust is 2 MB.
274 // The following test contains an endless loop, which will
275 // overrun the stack before timeout, if the stack size is
276 // not increased. See: RUST_MIN_STACK in test.
277 // ALSO NOTE: None of this works.
278 /*
279 #[test]
280 #[serial]
281 fn test_solve_timeout() {
282
283 let stack_size = get_stack_size();
284 if stack_size < 8000000 { return; }
285
286 let mut kb = KnowledgeBase::new();
287
288 // Make an infinite loop rule.
289 let rule = parse_rule("loop :- loop.").unwrap();
290 add_rules!(&mut kb, rule);
291
292 let query = parse_query("loop").unwrap();
293 let sn = query.base_node(&kb); // solution node
294
295 let actual = solve(Rc::clone(&sn));
296 let expected = format!("Query timed out after {} milliseconds.", S_TIMEOUT);
297 assert_eq!(expected, actual);
298
299 } // test_solve_timeout()
300 */
301
302 // NOTE: The default stack for Rust is 2 MB.
303 // The following test contains an endless loop, which will
304 // overrun the stack before timeout, if the stack size is
305 // not increased. See: RUST_MIN_STACK in test.
306 // ALSO NOTE: None of this works.
307 /*
308 #[test]
309 #[serial]
310 fn test_solve_all_timeout() {
311
312 let stack_size = get_stack_size();
313 if stack_size < 8000000 { return; }
314
315 let mut kb = KnowledgeBase::new();
316
317 // Make an infinite loop rule.
318 let rule = parse_rule("loop :- loop.").unwrap();
319 add_rules!(&mut kb, rule);
320
321 let query = parse_query("loop").unwrap();
322 let sn = query.base_node(&kb); // solution node
323
324 let actual = solve_all(Rc::clone(&sn));
325 let expected = format!("Query timed out after {} milliseconds.", S_TIMEOUT);
326 assert_eq!(expected, actual[0]);
327
328 } // test_solve_all_timeout()
329 */
330
331} // test