-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19 from leanprover-community/dev
Dev
- Loading branch information
Showing
13 changed files
with
477 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import Duper.Simp | ||
import Duper.Util.ProofReconstruction | ||
|
||
namespace Duper | ||
open Std | ||
open RuleM | ||
open SimpResult | ||
open Lean | ||
open Meta | ||
open LitSide | ||
|
||
initialize Lean.registerTraceClass `duper.rule.datatypeAcyclicity | ||
|
||
/-- Produces a list of (possibly duplicate) constructor subterms for `e` -/ | ||
partial def collectConstructorSubterms (e : Expr) : MetaM (Array Expr) := do | ||
let isConstructor ← matchConstCtor e.getAppFn' (fun _ => pure false) (fun _ _ => pure true) | ||
if isConstructor then | ||
let constructorSubterms ← e.getAppArgs.mapM (fun arg => collectConstructorSubterms arg) | ||
return constructorSubterms.flatten.push e | ||
else | ||
return #[e] | ||
|
||
/-- Returns `none` if `lit` does not compare constructor subterms, and returns `some litside` if `lit.litside` | ||
is a subterm of the constructor it is being compared to. Note that `lit.litside` may not itself be a constructor | ||
(e.g. `xs` is a constructor subterm of `x :: xs`) -/ | ||
def litComparesConstructorSubterms (lit : Lit) : MetaM (Option LitSide) := do | ||
let litTyIsInductive ← matchConstInduct lit.ty.getAppFn' (fun _ => pure false) (fun _ _ => pure true) | ||
if litTyIsInductive then | ||
trace[duper.rule.datatypeAcyclicity] "lit.ty {lit.ty} is an inductive datatype" | ||
-- If `e1` is a constructor subterm of `e2`, then `e1.weight ≤ e2.weight` | ||
if lit.lhs.weight < lit.rhs.weight then | ||
let rhsConstructorSubterms ← collectConstructorSubterms lit.rhs | ||
if rhsConstructorSubterms.contains lit.lhs then return some lhs | ||
else return none | ||
else if lit.rhs.weight < lit.lhs.weight then | ||
let lhsConstructorSubterms ← collectConstructorSubterms lit.lhs | ||
if lhsConstructorSubterms.contains lit.rhs then return some rhs | ||
else return none | ||
else | ||
if lit.lhs == lit.rhs then return some lhs | ||
else return none | ||
else -- `lit.ty` is not an inductive datatype so `lit` cannot be comparing constructor subterms | ||
trace[duper.rule.datatypeAcyclicity] "lit.ty {lit.ty} is not an inductive datatype" | ||
return none | ||
|
||
def mkDatatypeAcyclicityProof (removedLitNum : Nat) (litSide : LitSide) (premises : List Expr) | ||
(parents : List ProofParent) (transferExprs : Array Expr) (c : Clause) : MetaM Expr := do | ||
Meta.forallTelescope c.toForallExpr fun xs body => do | ||
let cLits := c.lits.map (fun l => l.map (fun e => e.instantiateRev xs)) | ||
let (parentsLits, appliedPremises, transferExprs) ← instantiatePremises parents premises xs transferExprs | ||
let parentLits := parentsLits[0]! | ||
let appliedPremise := appliedPremises[0]! | ||
let mut proofCases : Array Expr := Array.mkEmpty parentLits.size | ||
for i in [:parentLits.size] do | ||
let lit := parentLits[i]! | ||
if i == removedLitNum then -- `lit` is the equality asserting an acyclic constructor | ||
let proofCase ← Meta.withLocalDeclD `h lit.toExpr fun h => do | ||
let sizeOfInst ← mkAppOptM ``inferInstanceAs #[← mkAppOptM ``SizeOf #[lit.ty], none] | ||
let litTyMVar ← mkFreshExprMVar lit.ty | ||
let abstrLam ← mkLambdaFVars #[litTyMVar] $ ← mkAppOptM ``sizeOf #[some lit.ty, some sizeOfInst, some litTyMVar] | ||
let sizeOfEq ← mkAppM ``congrArg #[abstrLam, h] -- Has the type `sizeOf lit.lhs = sizeOf lit.rhs` | ||
let sizeOfEqFalseMVar ← mkFreshExprMVar $ ← mkAppM ``Not #[← inferType sizeOfEq] -- Has the type `¬(sizeOf lit.lhs = sizeOf lit.rhs)` | ||
let sizeOfEqFalseMVarId := sizeOfEqFalseMVar.mvarId! | ||
-- **TODO**: Figure out how to assign `sizeOfEqFalseMVar` an actual term (ideally generated by `simp_arith`) | ||
-- The below lines attempts to use the omega procedure to generate a term for `sizeOfEqFalseMVar`, but it doesn't work | ||
Elab.Tactic.Omega.omega [] sizeOfEqFalseMVarId -- Use the `omega` tactic to prove `¬(sizeOf lit.lhs = sizeOf lit.rhs)` | ||
let proofCase := mkApp2 (mkConst ``False.elim [levelZero]) body $ mkApp sizeOfEqFalseMVar sizeOfEq -- Has the type `body` | ||
trace[duper.rule.datatypeAcyclicity] "lit: {lit}, lit.ty: {lit.ty}, sizeOfInst: {sizeOfInst}, abstrLam: {abstrLam}, sizeOfEq: {sizeOfEq}" | ||
trace[duper.rule.datatypeAcyclicity] "sizeOfEqFalseMVar: {sizeOfEqFalseMVar}, proofCase: {proofCase}" | ||
Meta.mkLambdaFVars #[h] proofCase | ||
proofCases := proofCases.push proofCase | ||
else -- `lit` is not the equality to be removed | ||
let proofCase ← Meta.withLocalDeclD `h lit.toExpr fun h => do | ||
Meta.mkLambdaFVars #[h] $ ← orIntro (cLits.map Lit.toExpr) i h | ||
proofCases := proofCases.push proofCase | ||
let proof ← orCases (parentLits.map Lit.toExpr) proofCases | ||
Meta.mkLambdaFVars xs $ mkApp proof appliedPremise | ||
|
||
/-- Implements the acyclicity rules described in section 6.4 of https://arxiv.org/pdf/1611.02908 -/ | ||
def datatypeAcyclicity : MSimpRule := fun c => do | ||
let c ← loadClause c | ||
for i in [:c.lits.size] do | ||
let lit := c.lits[i]! | ||
match ← litComparesConstructorSubterms lit with | ||
| some side => | ||
if lit.sign then -- `lit` is never true so `lit` can be removed from `c` | ||
let res := c.eraseIdx i | ||
let yC ← yieldClause res "datatypeAcyclicity" none -- mkDatatypeAcyclicityProof i side | ||
trace[duper.rule.datatypeAcyclicity] "datatypeAcyclicity applied to {c.lits} to yield {yC.1}" | ||
return some #[yC] | ||
else -- `lit` is a tautology so the clause `c` can simply be removed | ||
trace[duper.rule.datatypeAcyclicity] "datatypeAcyclicity applied to remove {c.lits}" | ||
return some #[] | ||
| none => continue | ||
return none |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import Duper.Simp | ||
import Duper.Util.ProofReconstruction | ||
|
||
namespace Duper | ||
open Std | ||
open RuleM | ||
open SimpResult | ||
open Lean | ||
open Meta | ||
|
||
initialize Lean.registerTraceClass `duper.rule.datatypeDistinctness | ||
|
||
/-- Returns `none` if `lit` does not compare distinct constructors, returns `some false` if `lit` says two distinct | ||
constructors are equal, and returns `some true` if `lit` says two distinct constructors are not equal. -/ | ||
def litComparesDistinctConstructors (lit : Lit) : MetaM (Option Bool) := do | ||
let litTyIsInductive ← matchConstInduct lit.ty.getAppFn' (fun _ => pure false) (fun _ _ => pure true) | ||
if litTyIsInductive then | ||
trace[duper.rule.datatypeDistinctness] "lit.ty {lit.ty} is an inductive datatype" | ||
let lhsCtorOpt ← matchConstCtor lit.lhs.getAppFn' (fun _ => pure none) (fun cval lvls => pure (some (cval, lvls))) | ||
let rhsCtorOpt ← matchConstCtor lit.rhs.getAppFn' (fun _ => pure none) (fun cval lvls => pure (some (cval, lvls))) | ||
match lhsCtorOpt, rhsCtorOpt with | ||
| some lhsCtor, some rhsCtor => | ||
trace[duper.rule.datatypeDistinctness] "Both lit.lhs {lit.lhs} and lit.rhs {lit.rhs} have constructors as heads" | ||
if lhsCtor == rhsCtor then return none | ||
else return !lit.sign | ||
| _, _ => -- `lit` is not comparing two constructors | ||
trace[duper.rule.datatypeDistinctness] "Either lit.lhs {lit.lhs} or lit.rhs {lit.rhs} does not have a constructor at its head" | ||
return none | ||
else -- `lit.ty` is not an inductive datatype so `lit` cannot be comparing distinct constructors | ||
trace[duper.rule.datatypeDistinctness] "lit.ty {lit.ty} is not an inductive datatype" | ||
return none | ||
|
||
def mkDatatypeDistinctnessProof (refs : List (Option Nat)) (premises : List Expr) (parents: List ProofParent) | ||
(transferExprs : Array Expr) (c : Clause) : MetaM Expr := do | ||
Meta.forallTelescope c.toForallExpr fun xs body => do | ||
let cLits := c.lits.map (fun l => l.map (fun e => e.instantiateRev xs)) | ||
let (parentsLits, appliedPremises, transferExprs) ← instantiatePremises parents premises xs transferExprs | ||
let parentLits := parentsLits[0]! | ||
let appliedPremise := appliedPremises[0]! | ||
|
||
let mut proofCases : Array Expr := Array.mkEmpty parentLits.size | ||
for i in [:parentLits.size] do | ||
let lit := parentLits[i]! | ||
match ← litComparesDistinctConstructors lit with | ||
| some true => -- Throw an error because Dist-S⁻ should have been applied, not Dist-S⁺ | ||
throwError "mkDistPosProof :: Found a literal {lit} asserting that two distinct constructors are not equal" | ||
| some false => -- `lit` equates distinct instructors and should not appear in the final clause `c` | ||
let matchConstInductK := (fun ival lvls => pure (some (ival.name, ival.numParams, lvls))) | ||
let some (tyName, numParams, lvls) ← matchConstInduct lit.ty.getAppFn' (fun _ => pure none) matchConstInductK | ||
| throwError "mkDistPosProof :: Failed to find the inductive datatype corresponding to {lit.ty}" | ||
let proofCase ← Meta.withLocalDeclD `h lit.toExpr fun h => do | ||
let noConfusionArgs := #[some (mkConst ``False), none, none, some h] | ||
-- noConfusion first takes `numParams` arguments for parameters, so we need to add that many `none`s to the front of `noConfusionArgs` | ||
let noConfusionArgs := (Array.range numParams).map (fun _ => none) ++ noConfusionArgs | ||
let proofCase ← mkAppOptM' (mkConst (.str tyName "noConfusion") (0 :: lvls)) noConfusionArgs | ||
let proofCase := mkApp2 (mkConst ``False.elim [levelZero]) body proofCase | ||
Meta.mkLambdaFVars #[h] proofCase | ||
proofCases := proofCases.push proofCase | ||
| none => -- refs[i] should have the value (some j) where parentLits[i] == c[j] | ||
match refs[i]! with | ||
| none => | ||
throwError "There is a bug in mkDistPosProof (The refs invariant is not satisfied)" | ||
| some j => | ||
let proofCase ← Meta.withLocalDeclD `h parentLits[i]!.toExpr fun h => do | ||
Meta.mkLambdaFVars #[h] $ ← orIntro (cLits.map Lit.toExpr) j h | ||
proofCases := proofCases.push proofCase | ||
let proof ← orCases (parentLits.map Lit.toExpr) proofCases | ||
Meta.mkLambdaFVars xs $ mkApp proof appliedPremise | ||
|
||
/-- Implements the Dist-S⁺ and Dist-S⁻ rules described in https://arxiv.org/pdf/1611.02908 -/ | ||
def datatypeDistinctness : MSimpRule := fun c => do | ||
let c ← loadClause c | ||
/- Spec for newLits and refs: | ||
- If c.lits[i] equates distinct constructors (e.g. has the form `f s = g t` where `f` and `g` are constructors), | ||
then refs[i] = none | ||
- If c.lits[i] does not equate distinct constructors, then refs[i] = some j where newLits[j] = c.lits[i] -/ | ||
let mut newLits : List Lit := [] | ||
let mut refs : List (Option Nat) := [] | ||
for lit in c.lits do | ||
match ← litComparesDistinctConstructors lit with | ||
| some true => -- Apply Dist-S⁻ rule and delete the clause | ||
trace[duper.rule.datatypeDistinctness] "datatypeDistinctness- used to remove {c.lits} (due to lit: {lit})" | ||
return some #[] | ||
| some false => -- Apply Dist-S⁺ rule and remove `lit` from the clause | ||
refs := none :: refs | ||
| none => | ||
refs := (some newLits.length) :: refs | ||
newLits := lit :: newLits | ||
-- To achieve the desired spec for newLits and refs, I must reverse them | ||
newLits := newLits.reverse | ||
refs := refs.reverse | ||
if (newLits.length = c.lits.size) then | ||
return none | ||
else | ||
trace[duper.rule.datatypeDistinctness] "datatypeDistinctness+ applied with the newLits: {newLits}" | ||
let yc ← yieldClause (MClause.mk newLits.toArray) "datatypeDistinctness+" (some (mkDatatypeDistinctnessProof refs)) | ||
return some #[yc] | ||
|
||
end Duper |
Oops, something went wrong.