Skip to main content

prolog2/resolution/
proof.rs

1//! Proof search via SLD resolution with backtracking and predicate invention.
2
3use smallvec::SmallVec;
4
5use crate::{
6    heap::{heap::Heap, query_heap::QueryHeap, symbol_db::SymbolDB},
7    predicate_modules::{PredReturn, PredicateFunction},
8    program::{
9        clause::Clause,
10        hypothesis::Hypothesis,
11        predicate_table::{Predicate, PredicateTable},
12    },
13    resolution::{
14        build::{build, re_build_bound_arg_terms},
15        unification::unify,
16    },
17    Config,
18};
19
20/// A variable binding: `(source_addr, target_addr)` on the heap.
21pub type Binding = (usize, usize);
22#[derive(Debug)]
23pub(super) struct Env {
24    pub(super) goal: usize,
25    pub(super) bindings: Box<[Binding]>,
26    pub(super) choices: Vec<Clause>,
27    pred_function: Option<PredicateFunction>,
28    pred_function_tried: bool,
29    pub(crate) got_choices: bool,
30    pub(super) new_clause: bool,
31    pub(super) invent_pred: bool,
32    pub(super) children: usize,
33    pub(super) depth: usize,
34    total_choice_count: usize,
35    heap_point: usize, //How big was the heap after this goal was created
36}
37
38impl Env {
39    pub fn new(goal: usize, depth: usize, heap_point: usize) -> Self {
40        Env {
41            goal,
42            bindings: Box::new([]),
43            choices: Vec::new(),
44            pred_function: None,
45            pred_function_tried: false,
46            got_choices: false,
47            new_clause: false,
48            invent_pred: false,
49            children: 0,
50            depth,
51            total_choice_count: 0,
52            heap_point,
53        }
54    }
55
56    pub fn get_choices(
57        &mut self,
58        heap: &mut QueryHeap,
59        hypothesis: &mut Hypothesis,
60        predicate_table: &PredicateTable,
61    ) {
62        self.got_choices = true;
63        self.heap_point = heap.heap_len();
64        let (symbol, arity) = heap.str_symbol_arity(self.goal);
65
66        self.choices.clear();
67        self.choices.extend_from_slice(hypothesis);
68
69        if symbol == 0 {
70            // Add meta-rules (variable clauses) FIRST so they're at the start
71            if let Some(clauses) = predicate_table.get_variable_clauses(arity) {
72                self.choices.extend_from_slice(clauses);
73            }
74            // Add body predicates LAST so they're at the end and get popped first
75            // This ensures we try grounded predicates before inventing new ones
76            self.choices
77                .extend(predicate_table.get_body_clauses(arity).cloned());
78            self.total_choice_count = self.choices.len();
79        } else {
80            match predicate_table.get_predicate((symbol, arity)) {
81                Some(Predicate::Function(pred_function)) => {
82                    self.pred_function = Some(*pred_function)
83                }
84                Some(Predicate::Clauses(clauses)) => {
85                    self.choices.extend_from_slice(clauses);
86                    self.total_choice_count = self.choices.len();
87                }
88                None => {
89                    if let Some(clauses) = predicate_table.get_variable_clauses(arity) {
90                        self.choices.extend_from_slice(clauses);
91                        self.total_choice_count = self.choices.len();
92                    }
93                }
94            }
95        };
96    }
97
98    pub fn undo_try(
99        &mut self,
100        hypothesis: &mut Hypothesis,
101        heap: &mut QueryHeap,
102        h_clauses: &mut usize,
103        invented_preds: &mut usize,
104        debug: bool,
105    ) -> usize {
106        if debug {
107            eprintln!(
108                "[UNDO_TRY] goal={} addr={}",
109                heap.term_string(self.goal),
110                self.goal
111            );
112        }
113        if self.new_clause {
114            let clause = hypothesis.pop_clause();
115            if debug {
116                eprintln!(
117                    "[UNDO_CLAUSE] depth={} clause={}",
118                    self.depth,
119                    clause.to_string(heap)
120                );
121            }
122            *h_clauses -= 1;
123            self.new_clause = false;
124            if self.invent_pred {
125                *invented_preds -= 1;
126                self.invent_pred = false;
127            }
128        }
129        heap.unbind(&self.bindings);
130        heap.truncate(self.heap_point);
131        self.children
132    }
133
134    pub fn try_choices(
135        &mut self,
136        heap: &mut QueryHeap,
137        hypothesis: &mut Hypothesis,
138        allow_new_clause: bool,
139        allow_new_pred: bool,
140        predicate_table: &PredicateTable,
141        config: Config,
142        debug: bool,
143    ) -> Option<Vec<Env>> {
144        if self.depth > config.max_depth {
145            if debug {
146                eprintln!(
147                    "[FAIL_ON_DEPTH] depth={} goal={}",
148                    self.depth,
149                    heap.term_string(self.goal),
150                );
151            }
152            return None;
153        }
154
155        if let Some(pred_function) = self.pred_function {
156            if !self.pred_function_tried {
157                self.pred_function_tried = true;
158                match pred_function(heap, hypothesis, self.goal, predicate_table, config) {
159                    PredReturn::True => return Some(Vec::new()),
160                    PredReturn::False => {
161                        // Fall through to try clause-based choices
162                    }
163                    PredReturn::Binding(bindings) => {
164                        self.bindings = bindings.into_boxed_slice();
165                        heap.bind(&self.bindings);
166                        return Some(Vec::new());
167                    }
168                }
169            }
170        }
171
172        let mut choices_tried = 0;
173
174        'choices: while let Some(clause) = self.choices.pop() {
175            if debug {
176                eprintln!("[CALL] {}", clause.to_string(heap));
177            }
178
179            choices_tried += 1;
180            let head = clause.head();
181
182            if clause.meta() {
183                if !allow_new_clause {
184                    continue;
185                } else if !allow_new_pred
186                    && heap.str_symbol_arity(head).0 == 0
187                    && heap.str_symbol_arity(self.goal).0 == 0
188                {
189                    continue;
190                }
191            }
192
193            if let Some(mut substitution) = unify(heap, head, self.goal) {
194                for constraints in &hypothesis.constraints {
195                    if !substitution.check_constraints(&constraints, heap) {
196                        continue 'choices;
197                    }
198                }
199
200                if debug {
201                    eprintln!(
202                        "[MATCH] depth={} goal={} clause={}, choices_remaining={}",
203                        self.depth,
204                        heap.term_string(self.goal),
205                        clause.to_string(heap),
206                        self.choices.len()
207                    );
208                }
209
210                re_build_bound_arg_terms(heap, &mut substitution);
211
212                // Check if we need to invent a predicate BEFORE building goals
213                let mut invented_pred_addr: Option<usize> = None;
214                if clause.meta() {
215                    if heap.str_symbol_arity(head).0 == 0 && heap.str_symbol_arity(self.goal).0 == 0
216                    {
217                        self.invent_pred = true;
218                        let pred_symbol = SymbolDB::set_const(format!("pred_{}", hypothesis.len()));
219                        let pred_addr = heap.set_const(pred_symbol);
220                        substitution.set_arg(0, pred_addr);
221
222                        substitution =
223                            substitution.push((heap.deref_addr(self.goal + 1), pred_addr, true));
224                        invented_pred_addr = Some(pred_addr);
225                    }
226                }
227
228                // Build new goals (now with invented predicate if applicable)
229                let new_goals: Vec<usize> = clause
230                    .body()
231                    .iter()
232                    .map(|&body_literal| build(heap, &mut substitution, None, body_literal))
233                    .collect();
234
235                // Build hypothesis clause if meta
236                if clause.meta() {
237                    self.new_clause = true;
238
239                    let new_clause_literals: Vec<usize> = clause
240                        .iter()
241                        .map(|literal| build(heap, &mut substitution, clause.meta_vars, *literal))
242                        .collect();
243
244                    let mut constraints = Vec::with_capacity(16);
245                    for i in 0..32 {
246                        if clause.constrained_var(i) {
247                            constraints.push(unsafe { substitution.get_arg(i).unwrap_unchecked() });
248                        }
249                    }
250
251                    let new_clause = Clause::new(new_clause_literals, None, None);
252                    if debug {
253                        eprintln!(
254                            "[ADD_CLAUSE] depth={} goal={} clause={}",
255                            self.depth,
256                            heap.term_string(self.goal),
257                            new_clause.to_string(heap)
258                        );
259                        if invented_pred_addr.is_some() {
260                            eprintln!(
261                                "[INVENT_PRED] invented predicate for goal={}",
262                                heap.term_string(self.goal)
263                            );
264                        }
265                    }
266                    hypothesis.push_clause(new_clause, SmallVec::from_vec(constraints));
267                    if debug {
268                        eprintln!("[HYPOTHESIS]:\n{}", hypothesis.to_string(heap));
269                    }
270                }
271
272                self.bindings = substitution.get_bindings();
273                self.children = new_goals.len();
274                if debug {
275                    eprintln!("Bindings: {:?}", self.bindings);
276                }
277                heap.bind(&self.bindings);
278
279                return Some(
280                    new_goals
281                        .into_iter()
282                        .map(|goal| Env::new(goal, self.depth + 1, heap.heap_len()))
283                        .collect(),
284                );
285            }
286        }
287        if debug {
288            eprintln!(
289                "[NO_MATCH] depth={} goal={} tried {} choices, Originally had {} choices",
290                self.depth,
291                heap.term_string(self.goal),
292                choices_tried,
293                self.total_choice_count
294            );
295        }
296        None
297    }
298}
299
300/// The proof search engine.
301///
302/// Maintains a goal stack and iteratively resolves goals against the predicate
303/// table and the current hypothesis. Call [`Proof::prove`] repeatedly to
304/// enumerate solutions via backtracking.
305pub struct Proof<'a> {
306    stack: Vec<Env>,
307    pointer: usize,
308    pub hypothesis: Hypothesis,
309    pub heap: QueryHeap<'a>,
310    h_clauses: usize,
311    invented_preds: usize,
312}
313
314impl<'a> Proof<'a> {
315    pub fn new(heap: QueryHeap<'a>, goals: &[usize]) -> Self {
316        let hypothesis = Hypothesis::new();
317        let stack = goals
318            .iter()
319            .map(|goal| Env::new(*goal, 0, heap.heap_len()))
320            .collect();
321        Proof {
322            stack,
323            pointer: 0,
324            hypothesis,
325            heap,
326            h_clauses: 0,
327            invented_preds: 0,
328        }
329    }
330
331    /// Create a new proof with an existing hypothesis (for negation-as-failure checks)
332    pub fn with_hypothesis(heap: QueryHeap<'a>, goals: &[usize], hypothesis: Hypothesis) -> Self {
333        let h_clauses = hypothesis.len();
334        let stack = goals
335            .iter()
336            .map(|goal| Env::new(*goal, 0, heap.heap_len()))
337            .collect();
338        Proof {
339            stack,
340            pointer: 0,
341            hypothesis,
342            heap,
343            h_clauses,
344            invented_preds: 0,
345        }
346    }
347
348    pub fn prove(&mut self, predicate_table: &PredicateTable, config: Config) -> bool {
349        // Handle restart after previous success
350        if self.pointer == self.stack.len() {
351            if config.debug {
352                eprintln!(
353                    "[RESTART] pointer={} stack_len={} h_clauses={}",
354                    self.pointer,
355                    self.stack.len(),
356                    self.h_clauses
357                );
358                eprintln!("[RESTART_HYPOTHESIS]");
359                for (i, c) in self.hypothesis.iter().enumerate() {
360                    eprintln!("  [{}]: {}", i, c.to_string(&self.heap));
361                }
362            }
363
364            self.pointer -= 1;
365            self.stack[self.pointer].undo_try(
366                &mut self.hypothesis,
367                &mut self.heap,
368                &mut self.h_clauses,
369                &mut self.invented_preds,
370                config.debug,
371            );
372        }
373
374        while self.pointer < self.stack.len() {
375            if self.stack[self.pointer].got_choices {
376                if config.debug {
377                    eprintln!(
378                        "[RETRY] goal={} addr={}",
379                        self.heap.term_string(self.stack[self.pointer].goal),
380                        self.stack[self.pointer].goal
381                    );
382                }
383            } else {
384                self.stack[self.pointer].get_choices(
385                    &mut self.heap,
386                    &mut self.hypothesis,
387                    &predicate_table,
388                );
389                if config.debug {
390                    eprintln!(
391                        "[TRY] goal={} addr={}",
392                        self.heap.term_string(self.stack[self.pointer].goal),
393                        self.stack[self.pointer].goal
394                    );
395                }
396            }
397            match self.stack[self.pointer].try_choices(
398                &mut self.heap,
399                &mut self.hypothesis,
400                self.h_clauses < config.max_clause,
401                self.invented_preds < config.max_pred,
402                predicate_table,
403                config,
404                config.debug,
405            ) {
406                Some(new_goals) => {
407                    if self.stack[self.pointer].new_clause {
408                        self.h_clauses += 1;
409                    }
410                    if self.stack[self.pointer].invent_pred {
411                        self.invented_preds += 1;
412                    }
413                    self.pointer += 1;
414                    self.stack.splice(self.pointer..self.pointer, new_goals);
415                }
416                None => {
417                    if self.pointer == 0 {
418                        if config.debug {
419                            eprintln!("[FAILED] First goal exhausted");
420                        }
421                        return false;
422                    }
423                    // Reset got_choices for the goal we're backtracking FROM
424                    // This ensures if we reach this goal again via a different proof path,
425                    // it will get fresh choices based on the new hypothesis state
426                    self.stack[self.pointer].got_choices = false;
427                    self.stack[self.pointer].choices.clear();
428                    self.stack[self.pointer].pred_function_tried = false;
429
430                    self.pointer -= 1;
431                    let children = self.stack[self.pointer].undo_try(
432                        &mut self.hypothesis,
433                        &mut self.heap,
434                        &mut self.h_clauses,
435                        &mut self.invented_preds,
436                        config.debug,
437                    );
438                    self.stack
439                        .drain((self.pointer + 1)..(self.pointer + 1 + children));
440                }
441            }
442        }
443        true
444    }
445}
446
447#[cfg(test)]
448mod tests {}