(proof-new) Witness axiom reconstruction for purification skolems (#5289)
authorAndrew Reynolds <andrew.j.reynolds@gmail.com>
Sun, 18 Oct 2020 23:42:46 +0000 (18:42 -0500)
committerGitHub <noreply@github.com>
Sun, 18 Oct 2020 23:42:46 +0000 (18:42 -0500)
This formalizes the proofs of existentials that justify purification variables, e.g. those with witness form witness x. x = t for the term t they purify.

This PR generalizes EXISTS_INTRO to do this, and makes some simplifications to SkolemManager.

src/expr/proof_rule.h
src/expr/skolem_manager.cpp
src/expr/skolem_manager.h
src/smt/witness_form.cpp
src/smt/witness_form.h
src/theory/quantifiers/proof_checker.cpp

index 4000d4df7b310fb1895b819b62bc2d07d7bdee88..9c955d067f62bb825b2dc77cde9c0694e8d5af43 100644 (file)
@@ -670,10 +670,11 @@ enum class PfRule : uint32_t
   WITNESS_INTRO,
   // ======== Exists intro
   // Children: (P:F[t])
-  // Arguments: (t)
+  // Arguments: ((exists ((x T)) F[x]))
   // ----------------------------------------
   // Conclusion: (exists ((x T)) F[x])
-  // where x is a BOUND_VARIABLE unique to the pair F,t.
+  // This rule verifies that F[x] indeed matches F[t] with a substitution
+  // over x.
   EXISTS_INTRO,
   // ======== Skolemize
   // Children: (P:(exists ((x1 T1) ... (xn Tn)) F))
index e34813dcdac89fb9de6c0d771045b98738a39a12..a3647e84f739f07bdb490bffccca7ae0a2e95053 100644 (file)
@@ -33,6 +33,7 @@ struct SkolemFormAttributeId
 };
 typedef expr::Attribute<SkolemFormAttributeId, Node> SkolemFormAttribute;
 
+// Maps terms to their purify skolem variables
 struct PurifySkolemAttributeId
 {
 };
@@ -125,7 +126,7 @@ Node SkolemManager::skolemize(Node q,
     // method deterministic ensures that the proof checker (e.g. for
     // quantifiers) is capable of proving the expected value for conclusions
     // of proof rules, instead of an alpha-equivalent variant of a conclusion.
-    Node avp = getOrMakeBoundVariable(av, av);
+    Node avp = getOrMakeBoundVariable(av);
     ovarsW.push_back(avp);
     ovars.push_back(av);
   }
@@ -182,19 +183,9 @@ Node SkolemManager::mkBooleanTermVariable(Node t)
   return mkPurifySkolem(t, "", "", NodeManager::SKOLEM_BOOL_TERM_VAR);
 }
 
-Node SkolemManager::mkExistential(Node t, Node p)
+ProofGenerator* SkolemManager::getProofGenerator(Node t) const
 {
-  Assert(p.getType().isBoolean());
-  NodeManager* nm = NodeManager::currentNM();
-  Node v = getOrMakeBoundVariable(t, p);
-  Node bvl = nm->mkNode(BOUND_VAR_LIST, v);
-  Node psubs = p.substitute(TNode(t), TNode(v));
-  return nm->mkNode(EXISTS, bvl, psubs);
-}
-
-ProofGenerator* SkolemManager::getProofGenerator(Node t)
-{
-  std::map<Node, ProofGenerator*>::iterator it = d_gens.find(t);
+  std::map<Node, ProofGenerator*>::const_iterator it = d_gens.find(t);
   if (it != d_gens.end())
   {
     return it->second;
@@ -353,18 +344,16 @@ Node SkolemManager::getOrMakeSkolem(Node w,
   return k;
 }
 
-Node SkolemManager::getOrMakeBoundVariable(Node t, Node s)
+Node SkolemManager::getOrMakeBoundVariable(Node t)
 {
-  std::pair<Node, Node> key(t, s);
-  std::map<std::pair<Node, Node>, Node>::iterator it =
-      d_witnessBoundVar.find(key);
+  std::map<Node, Node>::iterator it = d_witnessBoundVar.find(t);
   if (it != d_witnessBoundVar.end())
   {
     return it->second;
   }
   TypeNode tt = t.getType();
   Node v = NodeManager::currentNM()->mkBoundVar(tt);
-  d_witnessBoundVar[key] = v;
+  d_witnessBoundVar[t] = v;
   return v;
 }
 
index ec5189d6dec9b34c4c6db4d74adb9e271432aab0..537c0b1e91ee7f78d25582fc374ae4e740a6a462 100644 (file)
@@ -159,13 +159,7 @@ class SkolemManager
    * Get proof generator for existentially quantified formula q. This returns
    * the proof generator that was provided in a call to mkSkolem above.
    */
-  ProofGenerator* getProofGenerator(Node q);
-  /**
-   * Make existential. Given t and p[t] where p is a formula, this returns
-   *   (exists ((x T)) p[x])
-   * where T is the type of t, and x is a variable unique to t,p.
-   */
-  Node mkExistential(Node t, Node p);
+  ProofGenerator* getProofGenerator(Node q) const;
   /**
    * Convert to witness form, where notice this recursively replaces *all*
    * skolems in n by their corresponding witness term. This is intended to be
@@ -197,7 +191,7 @@ class SkolemManager
    * Map to canonical bound variables. This is used for example by the method
    * mkExistential.
    */
-  std::map<std::pair<Node, Node>, Node> d_witnessBoundVar;
+  std::map<Node, Node> d_witnessBoundVar;
   /** Convert to witness or skolem form */
   static Node convertInternal(Node n, bool toWitness);
   /** Get or make skolem attribute for witness term w */
@@ -224,10 +218,11 @@ class SkolemManager
                  const std::string& comment = "",
                  int flags = NodeManager::SKOLEM_DEFAULT);
   /**
-   * Get or make bound variable unique to (s,t), for d_witnessBoundVar, where
-   * t and s are terms.
+   * Get or make bound variable unique to t whose type is the same as t. This
+   * is used to construct canonical bound variables e.g. for constructing
+   * bound variables for witness terms in the skolemize method above.
    */
-  Node getOrMakeBoundVariable(Node t, Node s);
+  Node getOrMakeBoundVariable(Node t);
 };
 
 }  // namespace CVC4
index e8d4f7356ff07abaa223201a88b85933502738d1..e4f0a56dcf3f8be335cacb69fc94a445d1e71b3c 100644 (file)
@@ -28,7 +28,8 @@ WitnessFormGenerator::WitnessFormGenerator(ProofNodeManager* pnm)
              "WfGenerator::TConvProofGenerator",
              nullptr,
              true),
-      d_wintroPf(pnm, nullptr, nullptr, "WfGenerator::LazyCDProof")
+      d_wintroPf(pnm, nullptr, nullptr, "WfGenerator::LazyCDProof"),
+      d_pskPf(pnm, nullptr, "WfGenerator::PurifySkolemProof")
 {
 }
 
@@ -92,6 +93,20 @@ Node WitnessFormGenerator::convertToWitnessForm(Node t)
           Node skBody = SkolemManager::getSkolemForm(curw[1]);
           Node exists = nm->mkNode(kind::EXISTS, curw[0], skBody);
           ProofGenerator* pg = skm->getProofGenerator(exists);
+          if (pg == nullptr)
+          {
+            // it may be a purification skolem
+            pg = convertExistsInternal(exists);
+            if (pg == nullptr)
+            {
+              // if no proof generator is provided, we justify the existential
+              // using the WITNESS_AXIOM trusted rule by providing it to the
+              // call to addLazyStep below.
+              Trace("witness-form")
+                  << "WitnessFormGenerator: No proof generator for " << exists
+                  << std::endl;
+            }
+          }
           // --------------------------- from pg
           // (exists ((x T)) (P x))
           // --------------------------- WITNESS_INTRO
@@ -141,5 +156,28 @@ WitnessFormGenerator::getWitnessFormEqs() const
   return d_eqs;
 }
 
+ProofGenerator* WitnessFormGenerator::convertExistsInternal(Node exists)
+{
+  Assert(exists.getKind() == kind::EXISTS);
+  if (exists[0].getNumChildren() == 1 && exists[1].getKind() == kind::EQUAL
+      && exists[1][0] == exists[0][0])
+  {
+    Node tpurified = exists[1][1];
+    Trace("witness-form") << "convertExistsInternal: infer purification "
+                          << exists << " for " << tpurified << std::endl;
+    // ------ REFL
+    // t = t
+    // ---------------- EXISTS_INTRO
+    // exists x. x = t
+    // The concluded existential is then used to construct the witness term
+    // via witness intro.
+    Node teq = tpurified.eqNode(tpurified);
+    d_pskPf.addStep(teq, PfRule::REFL, {}, {tpurified});
+    d_pskPf.addStep(exists, PfRule::EXISTS_INTRO, {teq}, {exists});
+    return &d_pskPf;
+  }
+  return nullptr;
+}
+
 }  // namespace smt
 }  // namespace CVC4
index 50c913ae9af483741951410c6ed2326ad22f5149..eb0cf30059e0f471a20086326f6204df3f1628ad 100644 (file)
@@ -81,6 +81,10 @@ class WitnessFormGenerator : public ProofGenerator
    * of this class (d_tcpg).
    */
   Node convertToWitnessForm(Node t);
+  /**
+   * Return a proof generator that can prove the given axiom exists.
+   */
+  ProofGenerator* convertExistsInternal(Node exists);
   /** The term conversion proof generator */
   TConvProofGenerator d_tcpg;
   /** The nodes we have already added rewrite steps for in d_tcpg */
@@ -89,6 +93,8 @@ class WitnessFormGenerator : public ProofGenerator
   std::unordered_set<Node, NodeHashFunction> d_eqs;
   /** Lazy proof storing witness intro steps */
   LazyCDProof d_wintroPf;
+  /** CDProof for justifying purification existentials */
+  CDProof d_pskPf;
 };
 
 }  // namespace smt
index e2a416120522b21fa7610e21c3551bfea6317407..8371492b556ce39d18fd8bb44660b9713d00d233 100644 (file)
@@ -14,6 +14,7 @@
 
 #include "theory/quantifiers/proof_checker.h"
 
+#include "expr/node_algorithm.h"
 #include "expr/skolem_manager.h"
 #include "theory/builtin/proof_checker.h"
 
@@ -43,8 +44,25 @@ Node QuantifiersProofRuleChecker::checkInternal(
     Assert(children.size() == 1);
     Assert(args.size() == 1);
     Node p = children[0];
-    Node t = args[0];
-    return sm->mkExistential(t, p);
+    Node exists = args[0];
+    if (exists.getKind() != kind::EXISTS || exists[0].getNumChildren() != 1)
+    {
+      return Node::null();
+    }
+    std::unordered_map<Node, Node, NodeHashFunction> subs;
+    if (!expr::match(exists[1], p, subs))
+    {
+      return Node::null();
+    }
+    // substitution must contain only the variable of the existential
+    for (const std::pair<const Node, Node>& s : subs)
+    {
+      if (s.first != exists[0][0])
+      {
+        return Node::null();
+      }
+    }
+    return exists;
   }
   else if (id == PfRule::WITNESS_INTRO)
   {