Skip to main content

prolog2/resolution/
proof.rs

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