Connect++ 0.6.0
A fast, readable connection prover for first-order logic.
Loading...
Searching...
No Matches
StackProver.cpp
1/*
2
3Copyright © 2023-24 Sean Holden. All rights reserved.
4
5*/
6/*
7
8This file is part of Connect++.
9
10Connect++ is free software: you can redistribute it and/or modify it
11under the terms of the GNU General Public License as published by the
12Free Software Foundation, either version 3 of the License, or (at your
13option) any later version.
14
15Connect++ is distributed in the hope that it will be useful, but WITHOUT
16ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
18more details.
19
20You should have received a copy of the GNU General Public License along
21with Connect++. If not, see <https://www.gnu.org/licenses/>.
22
23*/
24
25#include "StackProver.hpp"
26
31
32//----------------------------------------------------------------------
34: num_preds(0)
35, var_index()
36, fun_index()
37, term_index()
38, pred_index()
39, sub_stack()
40, results()
41, matrix()
42, path()
43, new_C()
44, lemmata()
45, u()
46, action(InferenceItemType::Start)
47, si(0)
48, current_depth_limit(0)
49, current_depth(1)
50, depth_limit_reached(false)
51, status()
52, tptp_conversion_string()
53, stack()
54, right_branch_stack()
55, backtrack(false)
56, proof_printer(&stack)
57, problem_path()
58, output_interval(params::output_frequency)
59, proof_count(0)
60, use_timeout(false)
61, end_time()
62, show(params::verbosity)
63, cnf_only(false)
64, conjecture_true(false)
65, conjecture_false(false)
66, conjecture_missing(false)
67, negated_conjecture_removed(false)
68, fof_has_axioms(false)
69, simplified_fof_has_axioms(false)
70{}
71//----------------------------------------------------------------------
73 num_preds = n;
75 path.set_num_preds(n);
76}
77//----------------------------------------------------------------------
78void StackProver::read_from_tptp_file(const string& file_name,
79 bool& found_conjecture,
80 size_t& fof_size) {
82 parser.parse_tptp_from_file(file_name);
83 status = parser.get_problem_status();
84 bool equality = parser.equality_used();
85 found_conjecture = parser.conjecture_present();
86 fof_size = parser.number_of_fof_formulas();
87 Predicate* equals_predicate = parser.get_equals_predicate();
96 parser.clear();
98 /*
99 * num_preds for Matrix is set by parser.
100 */
101 path.set_num_preds(num_preds);
102
103 if (params::show_clauses) {
104 std::exit(EXIT_SUCCESS);
105 }
106
107 if (status != string("") && params::first_parse) {
108 show(1, string("Problem status found: "));
109 show(1, status, true);
110 }
111 if (equality && params::add_equality_axioms) {
112 if (params::first_parse) {
113 show(1, string("Problem involves equality: adding axioms for =."), true);
114 params::first_parse = false;
115 }
116 add_equality_axioms(equals_predicate);
117 if (params::equality_axioms_at_start) {
119 }
120 }
121 /*
122 * Any further variables will be anonymous.
123 */
125 }
126//----------------------------------------------------------------------
128 /*
129 * Equality axioms as described in Handbook of Automated
130 * Reasoning, Volume 1, page 615.
131 */
132 Arity max_fun_arity = fun_index.find_maximum_arity();
133 Arity max_pred_arity = pred_index.find_maximum_arity();
134 /*
135 * You need at least three variables to describe these, and
136 * twice as many as the arity of the biggest predicate or
137 * function.
138 */
139 uint32_t max_arity = max_fun_arity;
140 if (max_pred_arity > max_arity)
141 max_arity = max_pred_arity;
142 if (max_arity < 3)
143 max_arity = 3;
144 vector<Term*> xs;
145 vector<Term*> ys;
146 string xvar("__eqx_");
147 string yvar("__eqy_");
148 for (size_t i = 0; i < max_arity; i++) {
149 Variable* xvarp = var_index.add_named_var(xvar + std::to_string(i));
150 Variable* yvarp = var_index.add_named_var(yvar + std::to_string(i));
151 xs.push_back(term_index.add_variable_term(xvarp));
152 ys.push_back(term_index.add_variable_term(yvarp));
153 }
154 /*
155 * How you construct these depends on which representation you're using.
156 * It's easy enough to show that the difference is only a case
157 * of swapping negations. See for example "Equality Preprocessing in
158 * Connection Calculi", Oliver and Otten.
159 */
160 bool pol = !params::positive_representation;
161 uint32_t n_added = 0;
162 /*
163 * Axiom for reflexivity.
164 */
165 vector<Term*> ref;
166 ref.push_back(xs[0]);
167 ref.push_back(xs[0]);
168 Literal reflexive(equals_predicate, ref, 2, pol);
169 Clause ref_c;
170 ref_c.add_lit(reflexive);
171 matrix.add_clause(ref_c, "equality");
172 n_added++;
173 /*
174 * Axiom for symmetry.
175 */
176 vector<Term*> xy;
177 xy.push_back(xs[0]);
178 xy.push_back(xs[1]);
179 vector<Term*> yx;
180 yx.push_back(xs[1]);
181 yx.push_back(xs[0]);
182 Literal sym1(equals_predicate, xy, 2, !pol);
183 Literal sym2(equals_predicate, yx, 2, pol);
184 Clause sym_c;
185 sym_c.add_lit(sym1);
186 sym_c.add_lit(sym2);
187 matrix.add_clause(sym_c, "equality");
188 n_added++;
189 /*
190 * Axiom for transitivity.
191 */
192 vector<Term*> yz;
193 yz.push_back(xs[1]);
194 yz.push_back(xs[2]);
195 vector<Term*> xz;
196 xz.push_back(xs[0]);
197 xz.push_back(xs[2]);
198 Literal tr1(equals_predicate, xy, 2, !pol);
199 Literal tr2(equals_predicate, yz, 2, !pol);
200 Literal tr3(equals_predicate, xz, 2, pol);
201 Clause tr_c;
202 tr_c.add_lit(tr1);
203 tr_c.add_lit(tr2);
204 tr_c.add_lit(tr3);
205 matrix.add_clause(tr_c, "equality");
206 n_added++;
207 /*
208 * Function substitution.
209 */
210 for (size_t j = 0; j < fun_index.get_size(); j++) {
211 Function* p = fun_index[j];
212 Arity ar = p->get_arity();
213 if (ar > 0) {
214 Clause c;
215 vector<Term*> x1xn;
216 vector<Term*> y1yn;
217 for (size_t i = 0; i < ar; i++) {
218 x1xn.push_back(xs[i]);
219 y1yn.push_back(ys[i]);
220 vector<Term*> xiyi;
221 xiyi.push_back(xs[i]);
222 xiyi.push_back(ys[i]);
223 Literal eq_lit(equals_predicate, xiyi, 2, !pol);
224 c.add_lit(eq_lit);
225 }
226 vector<Term*> t;
227 t.push_back(term_index.add_function_term(p, x1xn));
228 t.push_back(term_index.add_function_term(p, y1yn));
229 Literal f_lit(equals_predicate, t, 2, pol);
230 c.add_lit(f_lit);
231 matrix.add_clause(c, "equality");
232 n_added++;
233 }
234 }
235 /*
236 * Predicate substitution.
237 */
238 for (size_t j = 0; j < pred_index.get_num_preds(); j++) {
239 Predicate* p = pred_index[j];
240 Arity ar = p->get_arity();
241 if (ar > 0 && p != equals_predicate) {
242 Clause c;
243 vector<Term*> x1xn;
244 vector<Term*> y1yn;
245 for (size_t i = 0; i < ar; i++) {
246 x1xn.push_back(xs[i]);
247 y1yn.push_back(ys[i]);
248 vector<Term*> xiyi;
249 xiyi.push_back(xs[i]);
250 xiyi.push_back(ys[i]);
251 Literal eq_lit(equals_predicate, xiyi, 2, !pol);
252 c.add_lit(eq_lit);
253 }
254 Literal p_lit1(p, x1xn, ar, !pol);
255 Literal p_lit2(p, y1yn, ar, pol);
256 c.add_lit(p_lit1);
257 c.add_lit(p_lit2);
258 matrix.add_clause(c, "equality");
259 n_added++;
260 }
261 }
262 /*
263 * Distinct objects
264 */
265 Arity min_arity = fun_index.find_minimum_arity();
266 if (!params::no_distinct_objects && min_arity == 0) {
267 vector<Term*> all_distinct_constants;
268 vector<Term*> empty_args;
269 for (size_t i = 0; i < fun_index.get_size(); i++) {
270 Function* p = fun_index[i];
271 Arity ar = p->get_arity();
272 // Remember, you don't want to do this for Skolem constants.
273 string name = p->get_name();
274 string prefix = name.string::substr(0,params::unique_skolem_prefix.length());
275 bool is_skolem = (params::unique_skolem_prefix.string::compare(0, string::npos, prefix) == 0) &&
276 (params::unique_skolem_prefix.length() < name.length());
277 bool is_quoted = (name[0] == '\"' && name[name.size() - 1] == '\"');
278 if (ar == 0 &&
279 !is_skolem &&
280 (params::all_distinct_objects || is_quoted)) {
281 Term* t = term_index.add_function_term(p, empty_args);
282 all_distinct_constants.push_back(t);
283 }
284 }
285 size_t s = all_distinct_constants.size();
286 if (s > 1) {
287 for (size_t i = s - 1; i > 0; i--) {
288 for (size_t j = 0; j < i; j++) {
289 Clause c;
290 vector<Term*> args;
291 args.push_back(all_distinct_constants[i]);
292 args.push_back(all_distinct_constants[j]);
293 Literal eq_lit(equals_predicate, args, 2, !pol);
294 c.add_lit(eq_lit);
295 matrix.add_clause(c, "distinct_objects");
296 n_added++;
297 }
298 }
299 }
300 }
301 matrix.set_num_equals(n_added);
302}
303//----------------------------------------------------------------------
305 /*
306 * Don't waste your time if the regularity test applies.
307 */
308 if (params::use_regularity_test && !path.test_for_regularity(new_C))
309 return;
310 /*
311 * Don't try to populate axioms.
312 */
313 if (new_C.size() == 0) {
314 return;
315 }
316 /*
317 * Extensions and reductions.
318 */
319 stack[si].initialize(matrix, u);
320 /*
321 * Lemmas
322 */
323 if (params::use_lemmata) {
324 if (params::limit_lemmata)
326 else
328 }
329}
330//----------------------------------------------------------------------
332 bool result = ((params::limit_by_tree_depth && (current_depth >= current_depth_limit))
333 ||
334 (!params::limit_by_tree_depth && (stack[si].p.length() >= current_depth_limit)));
335 if (result)
336 depth_limit_reached = true;
337 return result;
338}
339//----------------------------------------------------------------------
340bool StackProver::axiom() const {
341 return stack[si].c.empty();
342}
343//----------------------------------------------------------------------
345 /*
346 * Add a new StackItem using the next action from the list stored
347 * in the StackItem currently in play. If necessary, also
348 * add something to right_branch_stack. Populate the new list of
349 * actions and update si.
350 */
351 action = stack[si].get_next_inference(matrix, u);
353 /*
354 * Why are the scope rules for switch so odd???
355 */
356 Clause old_C;
357 Lemmata old_Lem;
358 Literal neg_lit;
359 UnificationOutcome outcome;
360 Substitution sig;
361 Literal ext_L;
362 switch (action.T) {
363 //----------------------------------------------------------------------
364 //----------------------------------------------------------------------
365 //----------------------------------------------------------------------
366 // Lemmas.
367 //----------------------------------------------------------------------
368 //----------------------------------------------------------------------
369 //----------------------------------------------------------------------
370 case InferenceItemType::Lemma:
372 /*
373 * If you are restricting backtracking for lemmas then
374 * at this point you can remove all alternatives.
375 */
376 if (params::limit_bt_lemmas)
377 stack[si].restrict_backtrack();
378 /*
379 * Work out the new state.
380 */
381 new_C = stack[si].c;
383 path = stack[si].p;
384 lemmata = stack[si].l;
385 /*
386 * Extend the stack.
387 */
388 stack.emplace_back(StackItemType::Lemmata, new_C, path,
390 stack.back().set_this_action(action);
391 break;
392 //----------------------------------------------------------------------
393 //----------------------------------------------------------------------
394 //----------------------------------------------------------------------
395 // Reduction.
396 //----------------------------------------------------------------------
397 //----------------------------------------------------------------------
398 //----------------------------------------------------------------------
399 case InferenceItemType::Reduction:
401 /*
402 * If you are restricting backtracking for reductions then
403 * at this point you can remove all alternatives.
404 */
405 if (params::limit_bt_reductions)
406 stack[si].restrict_backtrack();
407 /*
408 * Reductions have a substitution, so apply it and remember
409 * in case you need to undo it later.
410 */
413 /*
414 * Work out the new state.
415 */
416 new_C = stack[si].c;
418 path = stack[si].p;
419 lemmata = stack[si].l;
421 /*
422 * Extend the stack.
423 */
424 stack.emplace_back(StackItemType::Reduction, new_C, path,
426 stack.back().set_this_action(action);
427 break;
428 //----------------------------------------------------------------------
429 //----------------------------------------------------------------------
430 //----------------------------------------------------------------------
431 // Extension.
432 //----------------------------------------------------------------------
433 //----------------------------------------------------------------------
434 //----------------------------------------------------------------------
435 case InferenceItemType::Extension:
437 /*
438 * You're going to generate new variables, so remember where to
439 * backtrack to.
440 */
442 /*
443 * This is an Extension, so you're going to add something to
444 * right_branch_stack.
445 */
446 path = stack[si].p;
447 old_C = stack[si].c;
449 old_Lem = stack[si].l;
450 old_Lem.push_back(action.L);
451 /*
452 * DON'T do populate_stack_item here! That can wait until you actually
453 * use the right branch. In fact it *has* to wait because we might
454 * apply substitutions that affect it.
455 */
456 right_branch_stack.emplace_back(StackItemType::RightBranch, old_C,
457 path, old_Lem, current_depth);
458 /*
459 * The right branch needs to know where to restrict backtracking.
460 */
461 right_branch_stack.back().set_bt_restriction_index(stack.size() - 1);
462 /*
463 * Now you can deal with the left branch.
464 */
467 /*
468 * Extensions have a substitution, so apply it and remember
469 * in case you need to undo it later.
470 */
471 neg_lit = action.L;
472 neg_lit.invert();
473 outcome = u(neg_lit, ext_L);
474 sig = u.get_substitution();
475 u.backtrack();
476 sig.apply();
477 sub_stack.push_all(sig);
478 /*
479 * Work out the new state.
480 */
481 path.push(action.L);
482 lemmata = stack[si].l;
483 /*
484 * Extend the stack.
485 */
486 stack.emplace_back(StackItemType::LeftBranch, new_C, path,
487 lemmata, sig, current_depth);
488 stack.back().set_this_action(action);
489 break;
490 default:
491 cerr << "PANIC!!! You should only have a lemmata, reduction or an extension here!"
492 << endl;
493 break;
494 }
495 /*
496 * Finally, move si on and work out the next bunch of possible actions.
497 */
498 si++;
500}
501//----------------------------------------------------------------------
503 /*
504 * When you're moving forward in the search and you hit an axiom,
505 * you need to see whether there are right branches still needing
506 * to be dealt with.
507 *
508 * Note that an empty right_branch_stack - meaning that you've
509 * found a proof - is dealt with by go().
510 *
511 * this_action does not get populated for the new StackItem in
512 * this case.
513 */
515 /*
516 * Move the next right branch to the stack.
517 */
518 stack.push_back(right_branch_stack.back());
519 right_branch_stack.pop_back();
520 /*
521 * Reset si.
522 */
523 si++;
524 /*
525 * Set up the new state.
526 */
527 new_C = stack[si].c;
528 path = stack[si].p;
529 lemmata = stack[si].l;
530 current_depth = stack[si].depth;
531 /*
532 * We deliberately delayed doing this, so do it now. (See
533 * documentation for StackProver::extend_with_action.)
534 */
536 /*
537 * At this point you are starting a right branch, so
538 * if you are restricting backtracking you remove all
539 * alternatives from the relevant point in the stack.
540 */
541 if (params::limit_bt_extensions) {
542 stack[stack[si].bt_restriction_index].restrict_backtrack();
543 }
544}
545//----------------------------------------------------------------------
547 backtrack = true;
548 stack.pop_back();
549 si--;
550 current_depth = stack[si].depth;
551}
552//----------------------------------------------------------------------
557//----------------------------------------------------------------------
561//----------------------------------------------------------------------
563 /*
564 * You're backtracking through a left branch, so you
565 * need to remember to get rid of the corresponding
566 * right branch as well.
567 */
568 right_branch_stack.pop_back();
572}
573//----------------------------------------------------------------------
591 /*
592 * If you're not limiting backtracking for extensions, or
593 * you *are*, but you're still exploring left trees, then this
594 * is straightforward: just put the item back on right_branch_stack
595 * so that it gets explored again later.
596 */
597 if (!params::limit_bt_extensions ||
598 ((params::limit_bt_extensions || params::limit_bt_all) &&
599 !params::limit_bt_extensions_left_tree)) {
600 /*
601 * Why is this necessary? After we backtrack we may make different
602 * substitutions, so in revisiting the right branch different
603 * possibilties may apply, so we re-compute them later.
604 */
605 stack.back().clear();
606 right_branch_stack.push_back(stack.back());
608 return;
609 }
610 /*
611 * We know we are limiting backtracking for extensions, and we
612 * are not exploring the left tree.
613 *
614 * Care is needed if you're not backtracking within the left
615 * part of the tree. You need to move back down the stack,
616 * deleting everything while also making sure that sub_stack
617 * and var_index are correctly maintained. Also, you don't
618 * want to return anything to right_branch_stack.
619 *
620 * This goes back to where the relevant literal was selected.
621 * Thus if you are not limiting the possibilities to only those
622 * for the first literal, it's open to the backtracking
623 * restriction to leave other possibilites to be tried, and
624 * they get picked up from this point.
625 */
626 if (params::limit_bt_extensions_left_tree) {
627 size_t target_index = stack[si].bt_restriction_index;
628 size_t current_index = stack.size() - 1;
629 while (current_index > target_index) {
630 switch (stack[si].item_type) {
631 case StackItemType::Lemmata:
632 break;
633 case StackItemType::Reduction:
635 break;
636 case StackItemType::LeftBranch:
639 break;
640 case StackItemType::RightBranch:
641 break;
642 default:
643 cerr << "Something is VERY WRONG!" << endl;
644 break;
645 }
647 current_index--;
648 }
649 }
650}
651//----------------------------------------------------------------------
652ProverResult StackProver::go() {
653 /*
654 * Having set up a single entry on the stack, containing a start
655 * state, search for a proof.
656 *
657 * Either you return by ending at the start state with nothing left
658 * to try, by finding a proof, by depth limiting or by timing out.
659 *
660 * The backtrack variable is important here - when true you are
661 * (surprise surprise) backtracking. So mostly each case in the
662 * following switch is divided according to whether you're going
663 * forward or backtracking.
664 */
665 while(true) {
666 /*
667 * Test for and deal with a timeout.
668 */
669 if (use_timeout && chrono::steady_clock::now() > end_time)
670 return ProverResult::TimeOut;
671 /*
672 * Say what's going on.
673 */
674 if (output_interval.tick() && params::verbosity >= 2) {
675 cout << cursor_symbols::Cursor::to_column(1);
677 cout << "Reductions: " << reductions_tried << " Extensions: " << extensions_tried;
678 cout << " Lemmata: " << lemmata_tried << " Right branches: " << right_branches_started;
679 cout << " Stack size: " << stack.size();
680 cout.flush();
681 }
682 /*
683 * si must point to the back of the stack at this point.
684 *
685 * Remember that extend_with_action will deal with this for you.
686 */
687 switch (stack[si].item_type) {
688 //----------------------------------------------------------------
689 //----------------------------------------------------------------
690 //----------------------------------------------------------------
691 // Deal with the start state. Essentially straightforward. Just
692 // deal with a completed search, otherwise work out the
693 // possibly actions and get on with it.
694 //----------------------------------------------------------------
695 //----------------------------------------------------------------
696 //----------------------------------------------------------------
697 case StackItemType::Start:
698 backtrack = false;
699 if (stack[si].no_more_inferences())
700 return ProverResult::OptionsExhausted;
701 else
703 break;
704 //----------------------------------------------------------------
705 //----------------------------------------------------------------
706 //----------------------------------------------------------------
707 // Lemmas. Again, mostly straightforward.
708 //----------------------------------------------------------------
709 //----------------------------------------------------------------
710 //----------------------------------------------------------------
711 case StackItemType::Lemmata:
712 /*
713 * Operation is essentially similar to the reduction case.
714 *
715 * First deal with moving forward.
716 */
717 if (!backtrack) {
718 if (axiom()) {
719 /*
720 * Either you've found a proof or you try a right branch.
721 */
722 if (right_branch_stack.empty())
723 return ProverResult::Valid;
724 else
726 }
727 /*
728 * Backtrack because of depth limiting.
729 */
730 else if (depth_limited() && params::depth_limit_all)
732 /*
733 * Backtrack because there's nothing left to try.
734 */
735 else if (stack[si].no_more_inferences())
737 /*
738 * There must be something left to try, so try it.
739 */
740 else
742 }
743 /*
744 * We are moving down the stack.
745 */
746 else {
747 /*
748 * If you're backtracking then you need to jump over axioms.
749 */
750 if (axiom())
752 /*
753 * If you're not at an axiom then you can start going forward
754 * again.
755 */
756 else
757 backtrack = false;
758 }
759 break;
760 //----------------------------------------------------------------
761 //----------------------------------------------------------------
762 //----------------------------------------------------------------
763 // Reduction. Almost identical to Lemmas, but note the
764 // slightly different backtracking requirement to take account
765 // of undoing the substitution.
766 //----------------------------------------------------------------
767 //----------------------------------------------------------------
768 //----------------------------------------------------------------
769 case StackItemType::Reduction:
770 /*
771 * We are moving up the stack.
772 */
773 if (!backtrack) {
774 if (axiom()) {
775 /*
776 * Either you've found a proof or you try a right branch.
777 */
778 if (right_branch_stack.empty())
779 return ProverResult::Valid;
780 else
782 }
783 /*
784 * Backtrack because of depth limiting.
785 */
786 else if (depth_limited() && params::depth_limit_all)
788 /*
789 * Backtrack because there's nothing left to try.
790 */
791 else if (stack[si].no_more_inferences())
793 /*
794 * There must be something left to try, so try it.
795 */
796 else
798 }
799 /*
800 * We are moving down the stack.
801 */
802 else {
803 /*
804 * If you're backtracking then you need to jump over axioms.
805 */
806 if (axiom())
808 /*
809 * If you're not at an axiom then you can start going forward
810 * again.
811 */
812 else
813 backtrack = false;
814 }
815 break;
816 //----------------------------------------------------------------
817 //----------------------------------------------------------------
818 //----------------------------------------------------------------
819 // Left branch of Extension. Mostly similar to the Reduction
820 // and Lemma cases, but the backtrack is again different to
821 // take care of the new variables, the substitution, and the
822 // right_branch_stack.
823 //----------------------------------------------------------------
824 //----------------------------------------------------------------
825 //----------------------------------------------------------------
826 case StackItemType::LeftBranch:
827 /*
828 * Operation is essentially similar to the Reduction and
829 * Lemma cases. See those for corresponding comments.
830 */
831 if (!backtrack) {
832 if (axiom())
834 else if (depth_limited() && !matrix.is_ground(stack[si].this_action.C_2))
836 else if (stack[si].no_more_inferences())
838 else
840 }
841 /*
842 * We are moving down the stack.
843 */
844 else {
845 if (axiom())
847 else
848 backtrack = false;
849 }
850 break;
851 //----------------------------------------------------------------
852 //----------------------------------------------------------------
853 //----------------------------------------------------------------
854 // Right branch of Extension. Mostly similar to the Reduction
855 // and Lemmata cases, but the backtrack is now much more
856 // delicate. See the documentation for right_extension_backtrack.
857 //----------------------------------------------------------------
858 //----------------------------------------------------------------
859 //----------------------------------------------------------------
860 case StackItemType::RightBranch:
861 /*
862 * Operation is essentially similar to the reduction case.
863 */
864 if (!backtrack) {
865 if (axiom()) {
866 if (right_branch_stack.empty())
867 return ProverResult::Valid;
868 else
870 }
871 else if (depth_limited())
873 else if (stack[si].no_more_inferences())
875 else
877 }
878 /*
879 * We are moving down the stack.
880 */
881 else {
882 if (axiom())
884 else
885 backtrack = false;
886 }
887 break;
888 //----------------------------------------------------------------
889 default:
890 cerr << "Something is VERY WRONG!" << endl;
891 break;
892 }
893 }
894 return ProverResult::Error;
895}
896//----------------------------------------------------------------------
897// Set start clauses according to current options.
898//
899// Note that restrict_start with !all_pos_neg_start and !conjecture_start
900// should never be available as it would make no sense.
901//
902// all_pos_neg_start along with conjecture_start are on a best-effort
903// basis - you may end up with nothing.
904//----------------------------------------------------------------------
906 results.clear();
907 size_t m_size = matrix.get_num_clauses();
908 /*
909 * Make sure noone has messed up and not set any start
910 * clause optionss.
911 */
914 /*
915 * The allstart option overides everything else so this is easy.
916 */
917 if (params::all_start) {
918 for (size_t i = 0; i < m_size; i++) {
919 results.push_back(StartClauseStatus::Start);
920 }
921 return;
922 }
923 bool first_clause_included = false;
924 /*
925 * params::all_pos_neg_start indicates use of positive
926 * or negative start clauses according to the representation.
927 * When you don't also have conjecture_start, either include
928 * all, or just the first possibility found.
929 */
930 if (params::all_pos_neg_start && !params::conjecture_start) {
931 for (size_t i = 0; i < m_size; i++) {
932 if (
933 (
934 (params::positive_representation && matrix.is_positive(i))
935 ||
936 (!params::positive_representation && matrix.is_negative(i))
937 )
938 &&
939 (!(params::restrict_start && first_clause_included))
940 ) {
941 results.push_back(StartClauseStatus::Start);
942 first_clause_included = true;
943 }
944 else {
945 results.push_back(StartClauseStatus::NoStart);
946 }
947 }
948 }
949 /*
950 * Similar case if you have conjecture_start but not all_pos_neg_start.
951 */
952 else if (!params::all_pos_neg_start && params::conjecture_start) {
953 for (size_t i = 0; i < m_size; i++) {
954 if (matrix.is_conjecture(i)
955 &&
956 (!(params::restrict_start && first_clause_included))) {
957 results.push_back(StartClauseStatus::Start);
958 first_clause_included = true;
959 }
960 else {
961 results.push_back(StartClauseStatus::NoStart);
962 }
963 }
964 }
965 /*
966 * The tricky case is when you want to combine pos/neg clauses,
967 * conjecture clauses, and restriction in some other way.
968 *
969 * Assume here that you have all_pos_neg_start and conjecture_start.
970 */
971 else {
972 for (size_t i = 0; i < m_size; i++) {
973 if (matrix.is_conjecture(i)
974 &&
975 (
976 (params::positive_representation && matrix.is_positive(i))
977 ||
978 (!params::positive_representation && matrix.is_negative(i))
979 )
980 &&
981 !(params::restrict_start && first_clause_included)) {
982 results.push_back(StartClauseStatus::Start);
983 first_clause_included = true;
984 }
985 else {
986 results.push_back(StartClauseStatus::NoStart);
987 }
988 }
989 }
990 /*
991 * There's a rare possibility that---because either there was no
992 * (negated) conjecture clause in the problem, or they were
993 * simplified out---at this point no start clause has been
994 * selected. If that's the case, either use all positive/negative
995 * clauses or just the first, according to the parameters set.
996 *
997 * Note: this must choose at least one start clause because problems
998 * without a positive and negative clause have already been solved.
999 */
1000 if (!first_clause_included) {
1001 if (params::verbosity > 2) {
1002 cout << "Note: you're asking for a conjecture to start, but there are none!" << endl;
1003 cout << " depending on the other parameter settings, we will use one or " << endl;
1004 cout << " all of the negative clauses." << endl;
1005 }
1006 // Don't forget this! If you get here you have a whole bunch of
1007 // NoStart in results!
1008 results.clear();
1009 for (size_t i = 0; i < m_size; i++) {
1010 if ((
1011 (params::positive_representation && matrix.is_positive(i))
1012 ||
1013 (!params::positive_representation && matrix.is_negative(i))
1014 ) &&
1015 !(params::restrict_start && first_clause_included)) {
1016 results.push_back(StartClauseStatus::Start);
1017 first_clause_included = true;
1018 }
1019 else {
1020 results.push_back(StartClauseStatus::NoStart);
1021 }
1022 }
1023 }
1024}
1025//----------------------------------------------------------------------
1026//----------------------------------------------------------------------
1027//----------------------------------------------------------------------
1028// Find a single proof using iterative deepening search.
1029//----------------------------------------------------------------------
1030//----------------------------------------------------------------------
1031//----------------------------------------------------------------------
1032ProverOutcome StackProver::prove() {
1036 if (params::deterministic_reorder) {
1037 deterministic_reorder(params::number_of_reorders);
1038 }
1039 if (params::random_reorder) {
1041 }
1042 if (params::random_reorder_literals) {
1044 }
1045 pair<bool, size_t> start_clause = matrix.find_start();
1046 /*
1047 * If the initial clauses don't have a positive and a negative
1048 * clause then the problem is trivial.
1049 */
1050 if (!start_clause.first) {
1051 return ProverOutcome::False;
1052 }
1053 /*
1054 * Deal with the possible ways to set up start clause(s) according to
1055 * the options. Keep track of which start clauses are in use, and if
1056 * necessary what outcomes for them have been seen so far.
1057 */
1059 /*
1060 * Main loop for iterative deepening search.
1061 */
1062 bool switched_to_complete = false;
1063 for (current_depth_limit = params::start_depth;
1064 current_depth_limit <= params::depth_limit;
1065 current_depth_limit += params::depth_increment) {
1066 /*
1067 * See if the parameters dictate that it's time to convert to
1068 * a complete search.
1069 */
1070 if (current_depth_limit >= params::switch_to_complete
1071 && !switched_to_complete) {
1073 /*
1074 * You may have changed some parameters, so make sure all relevant
1075 * start clauses now get tried.
1076 */
1078 current_depth_limit = params::start_depth;
1079 switched_to_complete = true;
1080 colour_string::ColourString cs(params::use_colours);
1081 show.nl(1);
1082 show(1, cs("Switching to complete search.").orange(), true);
1083 }
1084 show.nl(1);
1085 show(1, string("SEARCH TO DEPTH: "));
1086 show(1, std::to_string(current_depth_limit), true);
1087 /*
1088 * Generate each possible start move, and try to prove from
1089 * it.
1090 */
1091 size_t start_clause_index = 0;
1092 for (const Clause& C : matrix) {
1093 /*
1094 * Find the next start clause.
1095 */
1096 if (results[start_clause_index] == StartClauseStatus::NoStart
1097 || results[start_clause_index] == StartClauseStatus::False) {
1098 start_clause_index++;
1099 continue;
1100 }
1101 /*
1102 * Reset everything to use the current start clause.
1103 *
1104 * TODO: this is slightly messy at present because
1105 * the var_index doesn't necessarily get reset in the
1106 * most efficient way possible if a previous schedule
1107 * attempt timed out. (You'd need to go back down
1108 * the stack and backtrack it as necessary.) This is
1109 * of course irrelevant
1110 * because it just means you might not get full re-use of
1111 * new variable names, but all the same it would be nice
1112 * to fix.
1113 */
1117 /*
1118 * Say what's going on.
1119 */
1120 show(1, string("START from clause "));
1121 show(1, std::to_string(start_clause_index + 1));
1122 show(1, string(" of "));
1123 show(1, std::to_string(matrix.get_num_clauses()));
1124 show(2, string(": "));
1125 show(2, new_C.to_string(), true);
1126 cout.flush();
1127 /*
1128 * Set up the initial stack item containing the start clause, and
1129 * populate it.
1130 */
1131 StackItem start_item(StackItemType::Start, new_C, path, lemmata, 1);
1132 start_item.set_this_action(InferenceItem(InferenceItemType::Start, start_clause_index));
1133 stack.push_back(start_item);
1134 si = 0;
1136 /*
1137 * Start with depth 1, as this makes sense when reading output if you're
1138 * using depth of recursion or path length.
1139 */
1140 current_depth = 1;
1141 /*
1142 * Liftoff!!!
1143 */
1144 ProverResult result = go();
1145 /*
1146 * Dealing with the outcome takes some care and depends on
1147 * the parameters being used.
1148 */
1149 switch (result) {
1150 case ProverResult::Valid:
1151 proof_count++;
1152 if (params::build_proof) {
1153 if (params::generate_LaTeX_proof) {
1154 proof_printer.make_LaTeX(params::LaTeX_proof_path,
1156 matrix.make_LaTeX());
1157 }
1158 if (params::generate_Prolog_proof) {
1159 fs::path prolog_path = params::Prolog_proof_path;
1160 proof_printer.make_Prolog(prolog_path);
1161 matrix.write_to_prolog_file(params::Prolog_matrix_path);
1162 }
1163 }
1164 show(1, string(": Found proof number "));
1165 show(1, std::to_string(proof_count), true);
1166 return ProverOutcome::Valid;
1167 break;
1168 case ProverResult::Error:
1169 return ProverOutcome::Error;
1170 break;
1171 case ProverResult::TimeOut:
1172 return ProverOutcome::TimeOut;
1173 break;
1174 case ProverResult::OptionsExhausted:
1175 /*
1176 * If you ran out of options because you reached the depth
1177 * limit then you still need to continue.
1178 */
1179 if (depth_limit_reached) {
1180 show(1, string(": Depth limited"), true);
1181 }
1182 /*
1183 * If you ran out of options without reaching the depth limit, then
1184 * what you do depends on whether or not the search is complete.
1185 */
1186 else {
1188 results[start_clause_index] = StartClauseStatus::False;
1189 show(1, string(": False"), true);
1190 }
1191 }
1192 start_clause_index++;
1193 break;
1194 default:
1195 return ProverOutcome::Error;
1196 break;
1197 }
1198 /*
1199 * This is necessary. Yes, I've checked. Think about it: you need
1200 * one extra backtrack to undo the new variables generated when you
1201 * make a start clause.
1202 */
1204 }
1205 /*
1206 * Loop for start moves ends here.
1207 *
1208 * If everything was False then the theorem is False, otherwise
1209 * at least one attempt was depth-limited.
1210 */
1211 bool all_false = true;
1212 for (StartClauseStatus& outcome : results) {
1213 if (outcome == StartClauseStatus::Start) {
1214 all_false = false;
1215 break;
1216 }
1217 }
1218 if (all_false)
1219 return ProverOutcome::False;
1220 }
1221 /*
1222 * Iterative deepening loop ends here.
1223 */
1224 return ProverOutcome::PathLenLimit;
1225}
1226//----------------------------------------------------------------------
1227vector<pair<string, vector<size_t>>> StackProver::get_internal_proof() const {
1229}
1230//----------------------------------------------------------------------
1231void StackProver::show_stack() {
1232 cout << "--------------------------------------------------------" << endl;
1233 cout << "Stack:" << endl;
1234 cout << "--------------------------------------------------------" << endl;
1235 for (auto s : stack) {
1236 cout << s << endl;
1237 }
1238 cout << "--------------------------------------------------------" << endl;
1239}
1240//----------------------------------------------------------------------
1241void StackProver::show_right_stack() {
1242 cout << "--------------------------------------------------------" << endl;
1243 cout << "Right Stack:" << endl;
1244 cout << "--------------------------------------------------------" << endl;
1245 for (auto s : right_branch_stack) {
1246 cout << s << endl;
1247 }
1248 cout << "--------------------------------------------------------" << endl;
1249}
1250//----------------------------------------------------------------------
1252 verbose_print::VPrint show(params::verbosity);
1253 show(1, string("Reductions: "));
1254 show(1, std::to_string(reductions_tried));
1255 show(1, string(" Extensions: "));
1256 show(1, std::to_string(extensions_tried));
1257 show(1, string(" Lemmata: "));
1258 show(1, std::to_string(lemmata_tried));
1259 show(1, string(" Right branches: "));
1260 show(1, std::to_string(right_branches_started), true);
1261}
1262//----------------------------------------------------------------------
1265 double s = static_cast<double>(ms) / 1000.0;
1266 double ext_rate = (static_cast<double>(extensions_tried) / s);
1267 double red_rate = (static_cast<double>(reductions_tried) / s);
1268 double lem_rate = (static_cast<double>(lemmata_tried) / s);
1269 double right_rate = (static_cast<double>(right_branches_started) / s);
1270 double total_rate = (static_cast<double>(total) / s);
1271 cout << "Reductions: " << setw(15) << reductions_tried << " (" << static_cast<size_t>(red_rate) << "/s)" << endl;
1272 cout << "Extensions: " << setw(15) << extensions_tried << " (" << static_cast<size_t>(ext_rate) << "/s)" << endl;
1273 cout << "Lemmas: " << setw(15) << lemmata_tried << " (" << static_cast<size_t>(lem_rate) << "/s)" << endl;
1274 cout << "Right branches: " << setw(15) << right_branches_started << " (" << static_cast<size_t>(right_rate) << "/s)" << endl;
1275 cout << "Total: " << setw(15) << total << " (" << static_cast<size_t>(total_rate) << "/s)" << endl;
1276}
1277//----------------------------------------------------------------------
1278ostream& operator<<(ostream& out, const StackProver& p) {
1279 out << "Current state of the RecursiveProver object" << endl;
1280 out << "-------------------------------------------" << endl << endl;
1281 out << p.var_index << endl;
1282 out << p.fun_index << endl;
1283 out << p.term_index << endl;
1284 out << p.path << endl;
1285 out << p.matrix << endl;
1286 return out;
1287}
Representation of clauses.
Definition Clause.hpp:52
Clause make_copy_with_new_vars(VariableIndex &, TermIndex &) const
Make a copy of an entire clause, introducing new variables.
Definition Clause.cpp:107
void add_lit(const Literal &)
Add a literal, making sure you don't duplicate.
Definition Clause.cpp:96
size_t size() const
Straightforward get method.
Definition Clause.hpp:78
string to_string(bool=false) const
Convert to a string.
Definition Clause.cpp:244
void drop_literal(LitNum)
Get rid of the specified Literal.
Definition Clause.cpp:155
Basic representation of functions.
Definition Function.hpp:54
Arity get_arity() const
Most basic access function.
Definition Function.hpp:88
string get_name() const
Most basic access function.
Definition Function.hpp:84
Arity find_minimum_arity() const
Find the smallest arity appearing for any Function in the index.
size_t get_size() const
Self-explanatory.
Arity find_maximum_arity() const
Find the largest arity appearing for any Function in the index.
bool tick()
Definition Interval.hpp:52
Representation of the lemma list.
Definition Lemmata.hpp:49
void find_initial_lemmata(vector< InferenceItem > &, Clause &)
Find all lemmata that are applicable, but only for the initial Literal in a Clause.
Definition Lemmata.cpp:33
void find_all_lemmata(vector< InferenceItem > &, Clause &)
Find all lemmata that are applicable, given a Clause.
Definition Lemmata.cpp:56
void push_back(const Literal &)
Self-explanatory.
Definition Lemmata.cpp:28
Basic representation of literals, bundling together (pointers to) a Predicate, a collection of argume...
Definition Literal.hpp:50
void move_equals_to_start()
Self-explanatory.
Definition Matrix.cpp:208
ClauseNum get_num_clauses() const
Straightforward get method.
Definition Matrix.hpp:225
string make_LaTeX(bool=false) const
Make a usable LaTeX representation.
Definition Matrix.cpp:259
void get_literal_clause_pair(LitNum, size_t, Literal &, Clause &) const
Get a literal and clause from the index.
Definition Matrix.cpp:301
void add_clause(Clause &, string="")
Add a Clause to the Matrix and update the index accordingly.
Definition Matrix.cpp:97
bool is_negative(size_t i) const
Is a particular Clause negative?
Definition Matrix.hpp:279
bool is_conjecture(size_t i) const
Is a particular Clause a conjecture?
Definition Matrix.cpp:49
void write_to_prolog_file(const path &) const
Write to a file that can be read by Prolog.
Definition Matrix.cpp:272
void set_num_equals(uint32_t n)
Straightforward set method.
Definition Matrix.hpp:297
bool is_positive(size_t i) const
Is a particular Clause positive?
Definition Matrix.hpp:273
void set_num_preds(size_t)
Make an empty index.
Definition Matrix.cpp:44
pair< bool, size_t > find_start() const
Use a simple heuristic to find a good start clause.
Definition Matrix.cpp:54
bool is_ground(size_t i) const
Is a particular Clause ground?
Definition Matrix.hpp:285
Basic representation of predicates: here just names, ids and arities.
Definition Predicate.hpp:51
Arity get_arity() const
Basic get method.
Definition Predicate.hpp:90
size_t get_num_preds() const
Basic get method.
Arity find_maximum_arity() const
Find the largest arity appearing in the index.
void make_Prolog(const path &)
Convert to a form suitable for use by the Prolog proof checker and write to a file.
void make_LaTeX(const path &, const path &, const string &)
Convert to LaTeX and store in the specified file.
vector< pair< string, vector< size_t > > > make_internal() const
Make a simple data structure representing the proof stack.
Prover using a pair of stacks to conduct the proof search.
void set_num_preds(size_t)
Set the number of predicates.
bool depth_limit_reached
Self-explanatary.
void process_axiom_forward()
Start a right branch to continue from an axiom.
string status
Problem status, if found in input file.
void read_from_tptp_file(const string &, bool &, size_t &)
Obviously, reads a problem from a TPTP file.
vector< StartClauseStatus > results
This is populated by the StackProver::set_up_start_clauses method.
bool negated_conjecture_removed
Keep track of whether the parser simplified the conjecture away.
static uint32_t lemmata_tried
We'll be keeping some simple statistics about the search process.
void show_full_statistics(size_t) const
Display counts of number of extensions tried and so on, as well as numbers per second.
void lemmata_backtrack()
One of several different kinds of backtracking.
void reset_for_start()
Reset everything so that you can start from a specified start clause.
void random_reorder()
Randomly reorder the matrix.
uint32_t current_depth_limit
Self-explanatary.
InferenceItem action
Stores the next action from the current StackItem.
void set_up_start_clauses()
The start clauses to use depend on the settings, and the settings can change.
size_t num_preds
How many prdicates does the problem of interest have?
bool fof_has_axioms
Keep track of whether the parser found that it's an FOF problem with axioms before simplification.
vector< StackItem > stack
Main stack: this is constructed by the search process and, if completed, represents a proof.
ProverResult go()
This runs the proof search from a given Start Move.
Matrix matrix
A copy of the matrix you're working with.
void deterministic_reorder(uint32_t n)
Deterministically reorder the matrix n times.
PredicateIndex pred_index
This class needs one of each kind of index to keep track of Variables, Terms etc.
vector< StackItem > right_branch_stack
We build the proof by trying the left branches of extensions first: this stack keeps track of the rig...
ProofPrinter proof_printer
You need one of these to print LaTeX output or any kind of proof certificate.
void extend_with_action()
Take a single inference (action) and update the stacks accordingly.
bool cnf_only
Keep track of whether there were any FOF formulas in the problem file.
SimplePath path
At any point in the search process this is a copy of the path for the current node in the proof being...
FunctionIndex fun_index
This class needs one of each kind of index to keep track of Variables, Terms etc.
void populate_stack_item()
Fill the vector of possible actions with everything available.
Lemmata lemmata
At any point in the search process this is a copy of the list of lemmas for the current node in the p...
void show_statistics() const
Display counts of number of extensions tried and so on.
static uint32_t extensions_tried
We'll be keeping some simple statistics about the search process.
uint32_t current_depth
Self-explanatary.
string tptp_conversion_string
TPTP-friendly description of the clause conversion.
void add_equality_axioms(Predicate *)
After reading a problem in which = and/or != appears, add the axioms for equality.
bool simplified_fof_has_axioms
Keep track of whether the parser found that it's an FOF problem with axioms after simplification.
bool conjecture_missing
Keep track of whether the parser found a conjecture in the problem file.
TermIndex term_index
This class needs one of each kind of index to keep track of Variables, Terms etc.
uint32_t proof_count
If we're searching for multiple proofs, keep count of which one this is.
bool conjecture_false
Keep track of whether the parser found the conjecture to be false.
VariableIndex var_index
This class needs one of each kind of index to keep track of Variables, Terms etc.
void random_reorder_literals()
Randomly reorder the literals in each clause in the matrix.
size_t si
Index of the current StackItem.
void backtrack_once()
Basic, single step backtrack on the stack.
bool depth_limited()
Test for the depth limit.
bool conjecture_true
Keep track of whether the parser found the conjecture to be true.
void left_extension_backtrack()
One of several different kinds of backtracking.
static uint32_t right_branches_started
We'll be keeping some simple statistics about the search process.
bool backtrack
Are we moving up or down the stack?
chrono::steady_clock::time_point end_time
When do we stop because of a timeout?
vector< pair< string, vector< size_t > > > get_internal_proof() const
Get an internal representation of the proof stack.
Clause new_C
At any point in the search process this is a copy of the clause for the current node in the proof bei...
void right_extension_backtrack()
One of several different kinds of backtracking.
fs::path problem_path
Path for the problem of interest.
StackProver()
You only need a basic constructor.
bool use_timeout
Are we using a timeout?
void reduction_backtrack()
One of several different kinds of backtracking.
static uint32_t reductions_tried
We'll be keeping some simple statistics about the search process.
SubstitutionStack sub_stack
There is a separate stack to make application and removal of substitutions straightforward.
verbose_print::VPrint show
Set up printing according to verbosity.
Unifier u
We need a single Unifier to use throughout the process.
bool axiom() const
Test to see if you're at an axiom.
ProverOutcome prove()
Here is where the magic happens.
Interval output_interval
How often do you output updates about progress?
General representation of a substitution.
void apply() const
Apply a substitution everywhere.
void backtrack()
Remove variables from the stack, and remove substitutions as you go, as far back as the most recent b...
void push_all(Substitution &)
Take all the substitutions provided and add the corresponding variables to the stack.
Wrap up everything the TPTP parser needs to do inside a single class.
bool simplified_fof_has_axioms() const
Where there any axioms for an FOF problem after simplification?
string get_problem_status()
The parse tries to identify the problem status.
void clear()
Clear everything associated with the TPTP parser.
bool parse_tptp_from_file(const string &)
Main method to parse from a TPTP file to the data structures needed by the prover.
bool fof_conjecture_is_missing() const
Was there a conjecture clause in the problem file?
bool equality_used()
Did equality turn up anywhere in the parse?
bool conjecture_present() const
Did a conjecture turn up anywhere in the parse?
bool fof_has_axioms() const
Where there any axioms for an FOF problem before simplification?
string get_tptp_conversion_string() const
Self-explanatory.
bool fof_conjecture_is_false() const
Self-explanatory.
bool fof_conjecture_is_true() const
Self-explanatory.
Predicate * get_equals_predicate() const
If equality turned up anywhere in the parse it will have been turned into an actual Predicate,...
bool problem_is_cnf_only() const
Self-explanatory.
size_t number_of_fof_formulas() const
How many first-order formulas turned up in the parse?
bool fof_negated_conjecture_removed() const
Was there a conjecture clause that got simplified away?
General representation of terms.
Definition Term.hpp:62
Term * add_function_term(Function *, const vector< Term * > &)
Self-explanatory: add a Term containing a function to the index.
Definition TermIndex.cpp:56
Term * add_variable_term(Variable *)
Self-explanatory: add a Term containing a variable to the index.
Definition TermIndex.cpp:41
void backtrack()
Apply the backtracking process to the substitution that has been constructed.
Definition Unifier.cpp:195
Substitution get_substitution() const
Trivial get methods for the result.
Definition Unifier.hpp:122
Basic representation of variables.
Definition Variable.hpp:58
Variable * add_named_var(const string &)
Add a variable with the specified name to the index.
void set_all_names_added()
Call this to indicate that only anonymous variables can now be created.
void backtrack()
Backtrack to the last marker.
void add_backtrack_point()
Add a backtrack point.
Simple addition of colour to strings and ostreams.
static string erase_line(uint8_t n)
Definition cursor.hpp:158
void nl(uint8_t, uint8_t=1)
Full representation of inferences, beyond just the name.
LitNum Lindex
The index of the literal within the clause being used.
Substitution sigma
A copy of the substitution that makes the rule applicable. This may or may not be reusable,...
Literal L
The Literal that is used to make the inference.
size_t index_to_LC
Estensions are performance-critical. We store the index into the literal_clause_index,...
size_t index_in_LC_index
Estensions are performance-critical. We store the index into the literal_clause_index,...
InferenceItemType T
What kind of inference is this?
Stack items: each contains its own material for generating possible next inferences.
Definition StackItem.hpp:56
void set_this_action(const InferenceItem &inf_i)
Basic set method.
Structure containing all the command line and other options.
static void set_complete_parameters()
Change the parameters to make the search complete.
static bool no_start_options()
Self-explanatory.
static void correct_missing_start_options()
Self-explanatory.
static bool search_is_complete()
Self-explanatory.