feat(library/blast): add "recursor" (aka elimination/induction) action
This commit is contained in:
parent
0dc31227f8
commit
24ed427c2f
10 changed files with 376 additions and 12 deletions
|
@ -2,4 +2,4 @@ add_library(blast OBJECT expr.cpp state.cpp blast.cpp blast_tactic.cpp
|
|||
init_module.cpp simplifier.cpp simple_actions.cpp intros_action.cpp proof_expr.cpp
|
||||
options.cpp choice_point.cpp simple_strategy.cpp backward_action.cpp util.cpp
|
||||
gexpr.cpp revert_action.cpp subst_action.cpp no_confusion_action.cpp
|
||||
simplify_actions.cpp strategy.cpp)
|
||||
simplify_actions.cpp strategy.cpp recursor_action.cpp)
|
||||
|
|
|
@ -457,6 +457,7 @@ public:
|
|||
return m_tmp_local_generator.mk_tmp_local(type, bi);
|
||||
}
|
||||
expr whnf(expr const & e) { return m_tctx.whnf(e); }
|
||||
expr relaxed_whnf(expr const & e) { return m_tctx.relaxed_whnf(e); }
|
||||
expr infer_type(expr const & e) { return m_tctx.infer(e); }
|
||||
bool is_prop(expr const & e) { return m_tctx.is_prop(e); }
|
||||
bool is_def_eq(expr const & e1, expr const & e2) { return m_tctx.is_def_eq(e1, e2); }
|
||||
|
@ -565,11 +566,21 @@ bool is_relation(expr const & e, name & rop, expr & lhs, expr & rhs) {
|
|||
return g_blastenv->is_relation(e, rop, lhs, rhs);
|
||||
}
|
||||
|
||||
bool is_relation(expr const & e) {
|
||||
name rop; expr lhs, rhs;
|
||||
return is_relation(e, rop, lhs, rhs);
|
||||
}
|
||||
|
||||
expr whnf(expr const & e) {
|
||||
lean_assert(g_blastenv);
|
||||
return g_blastenv->whnf(e);
|
||||
}
|
||||
|
||||
expr relaxed_whnf(expr const & e) {
|
||||
lean_assert(g_blastenv);
|
||||
return g_blastenv->relaxed_whnf(e);
|
||||
}
|
||||
|
||||
expr infer_type(expr const & e) {
|
||||
lean_assert(g_blastenv);
|
||||
return g_blastenv->infer_type(e);
|
||||
|
|
|
@ -37,9 +37,11 @@ projection_info const * get_projection_info(name const & n);
|
|||
/** \brief Return true iff \c e is a relation application,
|
||||
and store the relation name, lhs and rhs in the output arguments. */
|
||||
bool is_relation(expr const & e, name & rop, expr & lhs, expr & rhs);
|
||||
bool is_relation(expr const & e);
|
||||
/** \brief Put the given expression in weak-head-normal-form with respect to the
|
||||
current state being processed by the blast tactic. */
|
||||
expr whnf(expr const & e);
|
||||
expr relaxed_whnf(expr const & e);
|
||||
/** \brief Normalize the given expression using the blast type context.
|
||||
This procedure caches results.
|
||||
\remark This procedure is intended for normalizing instances that are not subsingletons. */
|
||||
|
@ -129,6 +131,15 @@ public:
|
|||
tmp_type_context & operator*() { return *m_ctx; }
|
||||
};
|
||||
|
||||
class scope_curr_state {
|
||||
state m_saved;
|
||||
bool m_keep;
|
||||
public:
|
||||
scope_curr_state():m_saved(curr_state()), m_keep(false) {}
|
||||
~scope_curr_state() { if (!m_keep) curr_state() = m_saved; }
|
||||
void commit() { m_keep = true; }
|
||||
};
|
||||
|
||||
/**
|
||||
\brief Convert an external expression into a blast expression
|
||||
It converts meta-variables to blast meta-variables, and ensures the expressions
|
||||
|
|
289
src/library/blast/recursor_action.cpp
Normal file
289
src/library/blast/recursor_action.cpp
Normal file
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
|
||||
Author: Leonardo de Moura
|
||||
*/
|
||||
#include "kernel/instantiate.h"
|
||||
#include "kernel/inductive/inductive.h"
|
||||
#include "library/user_recursors.h"
|
||||
#include "library/blast/revert_action.h"
|
||||
#include "library/blast/blast.h"
|
||||
|
||||
namespace lean {
|
||||
namespace blast {
|
||||
optional<name> is_recursor_action_target(hypothesis_idx hidx) {
|
||||
state & s = curr_state();
|
||||
hypothesis const * h = s.get_hypothesis_decl(hidx);
|
||||
lean_assert(h);
|
||||
expr const & type = h->get_type();
|
||||
if (!is_app(type) && !is_constant(type))
|
||||
return optional<name>();
|
||||
if (is_relation(type))
|
||||
return optional<name>(); // we don't apply recursors to equivalence relations: =, ~, <->, etc.
|
||||
if (!h->is_assumption())
|
||||
return optional<name>(); // we only consider assumptions
|
||||
// TODO(Leo): more restrictions?
|
||||
// TODO(Leo): consider user-provided hints
|
||||
expr const & I = get_app_fn(type);
|
||||
if (!is_constant(I))
|
||||
return optional<name>();
|
||||
if (inductive::is_inductive_decl(env(), const_name(I)))
|
||||
return optional<name>(name(inductive::get_elim_name(const_name(I)))); // it is builtin recursive datatype
|
||||
list<name> Rs = get_recursors_for(env(), const_name(I));
|
||||
if (!Rs)
|
||||
return optional<name>();
|
||||
else
|
||||
return optional<name>(head(Rs)); // type has user-defined recursors
|
||||
}
|
||||
|
||||
unsigned get_num_minor_premises(name const & R) {
|
||||
return get_recursor_info(env(), R).get_num_minors();
|
||||
}
|
||||
|
||||
bool is_recursive_recursor(name const & R) {
|
||||
return get_recursor_info(env(), R).is_recursive();
|
||||
}
|
||||
|
||||
struct recursor_proof_step_cell : public proof_step_cell {
|
||||
bool m_dep;
|
||||
branch m_branch; // branch for backtracking
|
||||
expr m_proof; // recursor-application (the position where the goal-proofs are marked by the local constants).
|
||||
list<expr> m_goals; // type of each subgoal/branch encoded as a local constant
|
||||
list<expr> m_goal_proofs; // proofs generated so far
|
||||
|
||||
recursor_proof_step_cell(bool dep, branch const & b, expr const & pr, list<expr> const & goals, list<expr> const & goal_proofs):
|
||||
m_dep(dep), m_branch(b), m_proof(pr), m_goals(goals), m_goal_proofs(goal_proofs) {
|
||||
}
|
||||
|
||||
virtual ~recursor_proof_step_cell() {}
|
||||
|
||||
virtual action_result resolve(expr const & pr) const {
|
||||
state & s = curr_state();
|
||||
s.set_branch(m_branch);
|
||||
if (!m_dep) {
|
||||
// It is not a dependent elimination, so if pr did not use new hypothesis,
|
||||
// we don't need to investigate other branches.
|
||||
// This is also a form of non-chronological backtracking.
|
||||
expr const & goal = head(m_goals);
|
||||
unsigned arity = get_arity(mlocal_type(goal));
|
||||
expr it = pr;
|
||||
bool skip = true;
|
||||
for (unsigned i = 0; i < arity; i++) {
|
||||
if (!is_lambda(it)) {
|
||||
skip = false;
|
||||
break;
|
||||
}
|
||||
it = binding_body(it);
|
||||
if (!closed(it)) {
|
||||
skip = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skip) {
|
||||
lean_assert(closed(it));
|
||||
return action_result::solved(it);
|
||||
}
|
||||
}
|
||||
list<expr> new_goals = tail(m_goals);
|
||||
list<expr> new_prs = cons(pr, m_goal_proofs);
|
||||
if (empty(new_goals)) {
|
||||
buffer<expr> proof_args;
|
||||
buffer<expr> gs;
|
||||
to_buffer(m_goals, gs);
|
||||
expr const & rec = get_app_args(m_proof, proof_args);
|
||||
// update proof_args that are goals with their proofs
|
||||
unsigned i = proof_args.size();
|
||||
while (i > 0) {
|
||||
--i;
|
||||
if (!gs.empty() && proof_args[i] == gs.back()) {
|
||||
lean_assert(new_prs);
|
||||
proof_args[i] = head(new_prs);
|
||||
new_prs = tail(new_prs);
|
||||
}
|
||||
}
|
||||
return action_result::solved(mk_app(rec, proof_args));
|
||||
} else {
|
||||
s.pop_proof_step();
|
||||
s.push_proof_step(new recursor_proof_step_cell(m_dep, m_branch, m_proof, new_goals, new_prs));
|
||||
s.set_target(mlocal_type(head(new_goals)));
|
||||
return action_result::new_branch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
action_result recursor_action(hypothesis_idx hidx, name const & R) {
|
||||
state & s = curr_state();
|
||||
hypothesis const * h = s.get_hypothesis_decl(hidx);
|
||||
lean_assert(h);
|
||||
expr const & type = h->get_type();
|
||||
lean_assert(is_constant(get_app_fn(type)));
|
||||
|
||||
recursor_info rec_info = get_recursor_info(env(), R);
|
||||
|
||||
if (!rec_info.has_dep_elim() && s.target_depends_on(hidx)) {
|
||||
// recursor does does not support dependent elimination, but conclusion
|
||||
// depends on major premise
|
||||
return action_result::failed();
|
||||
}
|
||||
|
||||
buffer<expr> type_args;
|
||||
get_app_args(type, type_args);
|
||||
buffer<optional<expr>> params;
|
||||
for (optional<unsigned> const & pos : rec_info.get_params_pos()) {
|
||||
if (!pos) {
|
||||
params.push_back(none_expr());
|
||||
} else if (*pos >= type_args.size()) {
|
||||
return action_result::failed(); // major premise type is ill-formed
|
||||
} else {
|
||||
params.push_back(some_expr(type_args[*pos]));
|
||||
}
|
||||
}
|
||||
|
||||
buffer<expr> indices;
|
||||
list<unsigned> const & idx_pos = rec_info.get_indices_pos();
|
||||
for (unsigned pos : idx_pos) {
|
||||
if (pos >= type_args.size()) {
|
||||
return action_result::failed(); // major premise type is ill-formed");
|
||||
}
|
||||
expr const & idx = type_args[pos];
|
||||
if (!is_href(idx)) {
|
||||
return action_result::failed(); // argument of major premise type is not a href
|
||||
}
|
||||
for (unsigned i = 0; i < type_args.size(); i++) {
|
||||
if (i != pos && is_local(type_args[i]) && mlocal_name(type_args[i]) == mlocal_name(idx)) {
|
||||
return action_result::failed(); // argument of major premise is an index, but it occurs more than once
|
||||
}
|
||||
if (i > pos && // occurs after idx
|
||||
std::find(idx_pos.begin(), idx_pos.end(), i) != idx_pos.end() && // it is also an index
|
||||
is_href(type_args[i]) && // if it is not an index, it will fail anyway.
|
||||
s.hidx_depends_on(href_index(idx), href_index(type_args[i]))) {
|
||||
// argument of major premise type is an index, but its type depends on another index
|
||||
return action_result::failed();
|
||||
}
|
||||
}
|
||||
indices.push_back(idx);
|
||||
}
|
||||
|
||||
scope_curr_state save_state;
|
||||
hypothesis_idx_buffer_set to_revert;
|
||||
s.collect_direct_forward_deps(hidx, to_revert);
|
||||
for (auto i : indices)
|
||||
s.collect_direct_forward_deps(href_index(i), to_revert);
|
||||
revert_action(to_revert);
|
||||
|
||||
expr target = s.get_target();
|
||||
auto target_level = get_type_context().get_level_core(target);
|
||||
if (!target_level) return action_result::failed(); // failed to extract universe level of target
|
||||
|
||||
buffer<level> rec_lvls;
|
||||
expr const & I = get_app_fn(type);
|
||||
buffer<level> I_lvls;
|
||||
to_buffer(const_levels(I), I_lvls);
|
||||
bool found_target_lvl = false;
|
||||
for (unsigned idx : rec_info.get_universe_pos()) {
|
||||
if (idx == recursor_info::get_motive_univ_idx()) {
|
||||
rec_lvls.push_back(*target_level);
|
||||
found_target_lvl = true;
|
||||
} else {
|
||||
if (idx >= I_lvls.size())
|
||||
return action_result::failed(); // ill-formed recursor
|
||||
rec_lvls.push_back(I_lvls[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_target_lvl && !is_zero(*target_level)) {
|
||||
// recursor can only eliminate into Prop
|
||||
return action_result::failed();
|
||||
}
|
||||
expr rec = mk_constant(rec_info.get_name(), to_list(rec_lvls));
|
||||
|
||||
for (optional<expr> const & p : params) {
|
||||
if (p) {
|
||||
rec = mk_app(rec, *p);
|
||||
} else {
|
||||
// try type class resolution to synthesize argument
|
||||
expr rec_type = relaxed_whnf(infer_type(rec));
|
||||
if (!is_pi(rec_type))
|
||||
return action_result::failed(); // ill-formed recursor
|
||||
expr arg_type = binding_domain(rec_type);
|
||||
if (auto inst = mk_class_instance(arg_type)) {
|
||||
rec = mk_app(rec, *inst);
|
||||
} else {
|
||||
return action_result::failed(); // failed to generate instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expr motive = target;
|
||||
if (rec_info.has_dep_elim())
|
||||
motive = s.mk_lambda(h->get_self(), motive);
|
||||
motive = s.mk_lambda(indices, motive);
|
||||
rec = mk_app(rec, motive);
|
||||
|
||||
expr rec_type = relaxed_whnf(infer_type(rec));
|
||||
unsigned curr_pos = params.size() + 1 /* motive */;
|
||||
unsigned first_idx_pos = rec_info.get_first_index_pos();
|
||||
bool consumed_major = false;
|
||||
buffer<expr> new_goals;
|
||||
|
||||
while (is_pi(rec_type) && curr_pos < rec_info.get_num_args()) {
|
||||
if (first_idx_pos == curr_pos) {
|
||||
for (expr const & idx : indices) {
|
||||
rec = mk_app(rec, idx);
|
||||
rec_type = relaxed_whnf(instantiate(binding_body(rec_type), idx));
|
||||
if (!is_pi(rec_type))
|
||||
return action_result::failed(); // ill-formed type
|
||||
curr_pos++;
|
||||
}
|
||||
rec = mk_app(rec, h->get_self());
|
||||
rec_type = relaxed_whnf(instantiate(binding_body(rec_type), h->get_self()));
|
||||
consumed_major = true;
|
||||
curr_pos++;
|
||||
} else {
|
||||
expr new_type = binding_domain(rec_type);
|
||||
expr rec_arg;
|
||||
if (binding_info(rec_type).is_inst_implicit()) {
|
||||
auto inst = mk_class_instance(new_type);
|
||||
if (!inst) return action_result::failed(); // type class resolution failed
|
||||
rec_arg = *inst;
|
||||
} else {
|
||||
rec_arg = mk_fresh_local(new_type); // placeholder
|
||||
new_goals.push_back(rec_arg);
|
||||
}
|
||||
rec = mk_app(rec, rec_arg);
|
||||
rec_type = relaxed_whnf(instantiate(binding_body(rec_type), rec_arg));
|
||||
curr_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
if (curr_pos != rec_info.get_num_args() || !consumed_major)
|
||||
return action_result::failed(); // ill-formed recursor
|
||||
|
||||
save_state.commit();
|
||||
|
||||
if (new_goals.empty())
|
||||
return action_result::solved(rec);
|
||||
s.del_hypothesis(hidx);
|
||||
s.push_proof_step(new recursor_proof_step_cell(rec_info.has_dep_elim(), s.get_branch(), rec, to_list(new_goals), list<expr>()));
|
||||
s.set_target(mlocal_type(new_goals[0]));
|
||||
return action_result::new_branch();
|
||||
}
|
||||
|
||||
action_result recursor_action(hypothesis_idx hidx) {
|
||||
state & s = curr_state();
|
||||
hypothesis const * h = s.get_hypothesis_decl(hidx);
|
||||
lean_assert(h);
|
||||
expr const & type = h->get_type();
|
||||
expr const & I = get_app_fn(type);
|
||||
if (!is_constant(I))
|
||||
return action_result::failed();
|
||||
list<name> Rs = get_recursors_for(env(), const_name(I));
|
||||
for (auto R : Rs) {
|
||||
auto r = recursor_action(hidx, R);
|
||||
if (!failed(r))
|
||||
return r;
|
||||
}
|
||||
return action_result::failed();
|
||||
}
|
||||
}}
|
20
src/library/blast/recursor_action.h
Normal file
20
src/library/blast/recursor_action.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
|
||||
Author: Leonardo de Moura
|
||||
*/
|
||||
#pragma once
|
||||
#include "library/blast/action_result.h"
|
||||
#include "library/blast/hypothesis.h"
|
||||
namespace lean {
|
||||
namespace blast {
|
||||
/** \brief Return the name of the recursor that can be used with the given hypothesis */
|
||||
optional<name> is_recursor_action_target(hypothesis_idx hidx);
|
||||
|
||||
/** \brief Return the number of minor premises of the given recursor */
|
||||
unsigned get_num_minor_premises(name const & R);
|
||||
|
||||
action_result recursor_action(hypothesis_idx hidx, name const & R);
|
||||
action_result recursor_action(hypothesis_idx hidx);
|
||||
}}
|
|
@ -44,6 +44,16 @@ class simple_strategy : public strategy {
|
|||
return r;
|
||||
}
|
||||
|
||||
if (optional<name> R = is_recursor_action_target(*hidx)) {
|
||||
if (get_num_minor_premises(*R) == 1) {
|
||||
action_result r = recursor_action(*hidx, *R);
|
||||
if (!failed(r)) {
|
||||
if (!preprocess) display_action("recursor");
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return action_result::new_branch();
|
||||
}
|
||||
|
||||
|
|
|
@ -318,7 +318,8 @@ public:
|
|||
void set_target(expr const & t);
|
||||
expr const & get_target() const { return m_branch.m_target; }
|
||||
/** \brief Return true iff the target depends on the given hypothesis */
|
||||
bool target_depends_on(expr const & h) const { return m_branch.m_target_deps.contains(href_index(h)); }
|
||||
bool target_depends_on(hypothesis_idx hidx) const { return m_branch.m_target_deps.contains(hidx); }
|
||||
bool target_depends_on(expr const & h) const { return target_depends_on(href_index(h)); }
|
||||
|
||||
/************************
|
||||
Proof steps
|
||||
|
|
|
@ -1052,10 +1052,18 @@ expr type_context::infer_lambda(expr e) {
|
|||
return r;
|
||||
}
|
||||
|
||||
/** \brief Make sure \c e is a sort, if it is not throw an exception using \c ref as a reference */
|
||||
void type_context::ensure_sort(expr const & e, expr const & /* ref */) {
|
||||
// Remark: for simplicity reasons, we just fail if \c e is not a sort.
|
||||
if (!is_sort(e))
|
||||
optional<level> type_context::get_level_core(expr const & A) {
|
||||
expr A_type = relaxed_whnf(infer(A));
|
||||
if (is_sort(A_type))
|
||||
return some_level(sort_level(A_type));
|
||||
else
|
||||
return none_level();
|
||||
}
|
||||
|
||||
level type_context::get_level(expr const & A) {
|
||||
if (auto r = get_level_core(A))
|
||||
return *r;
|
||||
else
|
||||
throw exception("infer type failed, sort expected");
|
||||
}
|
||||
|
||||
|
@ -1065,17 +1073,13 @@ expr type_context::infer_pi(expr const & e0) {
|
|||
expr e = e0;
|
||||
while (is_pi(e)) {
|
||||
expr d = instantiate_rev(binding_domain(e), ls.size(), ls.data());
|
||||
expr d_type = relaxed_whnf(infer(d));
|
||||
ensure_sort(d_type, e0);
|
||||
us.push_back(sort_level(d_type));
|
||||
us.push_back(get_level(d));
|
||||
expr l = mk_tmp_local(d, binding_info(e));
|
||||
ls.push_back(l);
|
||||
e = binding_body(e);
|
||||
}
|
||||
e = instantiate_rev(e, ls.size(), ls.data());
|
||||
expr e_type = relaxed_whnf(infer(e));
|
||||
ensure_sort(e_type, e0);
|
||||
level r = sort_level(e_type);
|
||||
level r = get_level(e);
|
||||
unsigned i = ls.size();
|
||||
bool imp = m_env.impredicative();
|
||||
while (i > 0) {
|
||||
|
|
|
@ -443,6 +443,11 @@ public:
|
|||
*/
|
||||
bool is_def_eq(expr const & e1, expr const & e2);
|
||||
|
||||
/** \brief Return the universe level for type A. If A is not a type return none. */
|
||||
optional<level> get_level_core(expr const & A);
|
||||
/** \brief Similar to previous method, but throws exception instead */
|
||||
level get_level(expr const & A);
|
||||
|
||||
/** \brief If \c type is a type class, return its name */
|
||||
optional<name> is_class(expr const & type);
|
||||
|
||||
|
|
13
tests/lean/run/blast14.lean
Normal file
13
tests/lean/run/blast14.lean
Normal file
|
@ -0,0 +1,13 @@
|
|||
set_option blast.init_depth 10
|
||||
set_option blast.inc_depth 1000
|
||||
|
||||
definition lemma1 (p q : Prop) : p ∧ q → q ∧ p :=
|
||||
by blast
|
||||
|
||||
definition lemma2 (p q r s : Prop) : r ∧ s → p ∧ q → q ∧ p :=
|
||||
by blast
|
||||
|
||||
print lemma2 -- unnecessary and.rec's are not included
|
||||
|
||||
example (p q : Prop) : p ∧ p ∧ q ∧ q → q ∧ p :=
|
||||
by blast
|
Loading…
Reference in a new issue