(proof-new) Updates to proof node updater algorithm (#5088)
authorAndrew Reynolds <andrew.j.reynolds@gmail.com>
Fri, 18 Sep 2020 02:38:42 +0000 (21:38 -0500)
committerGitHub <noreply@github.com>
Fri, 18 Sep 2020 02:38:42 +0000 (21:38 -0500)
This updates the proof node updater so that we apply updates to a fixed point, stopping if the proof node is not updated or if the callback explicitly tells us not to continue. This also fixes an issue where proof nodes would be updated to SCOPE and incorrectly traversed after updating.

This additionally adds debugging feature to proof node updater to track the moment at which an unexpected free assumption is introduced by an update.

src/expr/proof_node_updater.cpp
src/expr/proof_node_updater.h
src/smt/proof_post_processor.cpp
src/smt/proof_post_processor.h

index 55eebb0d1dba4db945343f6c4b9eacb82ea26465..3368cb8569cddbd542b30c59ba9d3d9e0726040a 100644 (file)
@@ -26,31 +26,58 @@ bool ProofNodeUpdaterCallback::update(Node res,
                                       PfRule id,
                                       const std::vector<Node>& children,
                                       const std::vector<Node>& args,
-                                      CDProof* cdp)
+                                      CDProof* cdp,
+                                      bool& continueUpdate)
 {
   return false;
 }
 
 ProofNodeUpdater::ProofNodeUpdater(ProofNodeManager* pnm,
                                    ProofNodeUpdaterCallback& cb)
-    : d_pnm(pnm), d_cb(cb)
+    : d_pnm(pnm), d_cb(cb), d_debugFreeAssumps(false)
 {
 }
 
 void ProofNodeUpdater::process(std::shared_ptr<ProofNode> pf)
+{
+  if (d_debugFreeAssumps)
+  {
+    if (Trace.isOn("pfnu-debug"))
+    {
+      Trace("pfnu-debug2") << "Initial proof: " << *pf.get() << std::endl;
+      Trace("pfnu-debug") << "ProofNodeUpdater::process" << std::endl;
+      Trace("pfnu-debug") << "Expected free assumptions: " << std::endl;
+      for (const Node& fa : d_freeAssumps)
+      {
+        Trace("pfnu-debug") << "- " << fa << std::endl;
+      }
+      std::vector<Node> assump;
+      expr::getFreeAssumptions(pf.get(), assump);
+      Trace("pfnu-debug") << "Current free assumptions: " << std::endl;
+      for (const Node& fa : assump)
+      {
+        Trace("pfnu-debug") << "- " << fa << std::endl;
+      }
+    }
+  }
+  processInternal(pf, d_freeAssumps);
+}
+
+void ProofNodeUpdater::processInternal(std::shared_ptr<ProofNode> pf,
+                                       const std::vector<Node>& fa)
 {
   Trace("pf-process") << "ProofNodeUpdater::process" << std::endl;
-  std::unordered_map<ProofNode*, bool> visited;
-  std::unordered_map<ProofNode*, bool>::iterator it;
-  std::vector<ProofNode*> visit;
-  ProofNode* cur;
-  visit.push_back(pf.get());
-  std::map<Node, ProofNode*>::iterator itc;
+  std::unordered_map<std::shared_ptr<ProofNode>, bool> visited;
+  std::unordered_map<std::shared_ptr<ProofNode>, bool>::iterator it;
+  std::vector<std::shared_ptr<ProofNode>> visit;
+  std::shared_ptr<ProofNode> cur;
+  visit.push_back(pf);
+  std::map<Node, std::shared_ptr<ProofNode>>::iterator itc;
   // A cache from formulas to proof nodes that are in the current scope.
   // Notice that we make a fresh recursive call to process if the current
   // rule is SCOPE below.
-  std::map<Node, ProofNode*> resCache;
-  TNode res;
+  std::map<Node, std::shared_ptr<ProofNode>> resCache;
+  Node res;
   do
   {
     cur = visit.back();
@@ -64,26 +91,50 @@ void ProofNodeUpdater::process(std::shared_ptr<ProofNode> pf)
       {
         // already have a proof
         visited[cur] = true;
-        d_pnm->updateNode(cur, itc->second);
+        d_pnm->updateNode(cur.get(), itc->second.get());
       }
       else
       {
         visited[cur] = false;
-        // run update first
-        runUpdate(cur);
+        // run update to a fixed point
+        bool continueUpdate = true;
+        while (runUpdate(cur, fa, continueUpdate) && continueUpdate)
+        {
+          Trace("pf-process-debug") << "...updated proof." << std::endl;
+        }
+        if (!continueUpdate)
+        {
+          // no further changes should be made to cur according to the callback
+          Trace("pf-process-debug")
+              << "...marked to not continue update." << std::endl;
+          continue;
+        }
         visit.push_back(cur);
+        // If we are not the top-level proof, we were a scope, or became a
+        // scope after updating, we need to make a separate recursive call to
+        // this method.
+        if (cur->getRule() == PfRule::SCOPE && cur != pf)
+        {
+          std::vector<Node> nfa;
+          // if we are debugging free assumptions, update the set
+          if (d_debugFreeAssumps)
+          {
+            nfa.insert(nfa.end(), fa.begin(), fa.end());
+            const std::vector<Node>& args = cur->getArguments();
+            nfa.insert(nfa.end(), args.begin(), args.end());
+            Trace("pfnu-debug2")
+                << "Process new scope with " << args << std::endl;
+          }
+          // Process in new call separately, since we should not cache
+          // the results of proofs that have a different scope.
+          processInternal(cur, nfa);
+          continue;
+        }
         const std::vector<std::shared_ptr<ProofNode>>& ccp = cur->getChildren();
         // now, process children
         for (const std::shared_ptr<ProofNode>& cp : ccp)
         {
-          if (cp->getRule() == PfRule::SCOPE)
-          {
-            // Process in new call separately, since we should not cache
-            // the results of proofs that have a different scope.
-            process(cp);
-            continue;
-          }
-          visit.push_back(cp.get());
+          visit.push_back(cp);
         }
       }
     }
@@ -92,17 +143,28 @@ void ProofNodeUpdater::process(std::shared_ptr<ProofNode> pf)
       visited[cur] = true;
       // cache result
       resCache[res] = cur;
+      if (d_debugFreeAssumps)
+      {
+        // We have that npn is a node we occurring the final updated version of
+        // the proof. We can now debug based on the expected set of free
+        // assumptions.
+        Trace("pfnu-debug") << "Ensure update closed..." << std::endl;
+        pfnEnsureClosedWrt(
+            cur.get(), fa, "pfnu-debug", "ProofNodeUpdater:finalize");
+      }
     }
   } while (!visit.empty());
   Trace("pf-process") << "ProofNodeUpdater::process: finished" << std::endl;
 }
 
-void ProofNodeUpdater::runUpdate(ProofNode* cur)
+bool ProofNodeUpdater::runUpdate(std::shared_ptr<ProofNode> cur,
+                                 const std::vector<Node>& fa,
+                                 bool& continueUpdate)
 {
   // should it be updated?
-  if (!d_cb.shouldUpdate(cur))
+  if (!d_cb.shouldUpdate(cur.get()))
   {
-    return;
+    return false;
   }
   PfRule id = cur->getRule();
   // use CDProof to open a scope for which the callback updates
@@ -120,15 +182,43 @@ void ProofNodeUpdater::runUpdate(ProofNode* cur)
       << "Updating (" << cur->getRule() << ")..." << std::endl;
   Node res = cur->getResult();
   // only if the callback updated the node
-  if (d_cb.update(res, id, ccn, cur->getArguments(), &cpf))
+  if (d_cb.update(res, id, ccn, cur->getArguments(), &cpf, continueUpdate))
   {
     std::shared_ptr<ProofNode> npn = cpf.getProofFor(res);
+    std::vector<Node> fullFa;
+    if (d_debugFreeAssumps)
+    {
+      expr::getFreeAssumptions(cur.get(), fullFa);
+      Trace("pfnu-debug") << "Original proof : " << *cur << std::endl;
+    }
     // then, update the original proof node based on this one
     Trace("pf-process-debug") << "Update node..." << std::endl;
-    d_pnm->updateNode(cur, npn.get());
+    d_pnm->updateNode(cur.get(), npn.get());
     Trace("pf-process-debug") << "...update node finished." << std::endl;
+    if (d_debugFreeAssumps)
+    {
+      fullFa.insert(fullFa.end(), fa.begin(), fa.end());
+      // We have that npn is a node we occurring the final updated version of
+      // the proof. We can now debug based on the expected set of free
+      // assumptions.
+      Trace("pfnu-debug") << "Ensure update closed..." << std::endl;
+      pfnEnsureClosedWrt(
+          npn.get(), fullFa, "pfnu-debug", "ProofNodeUpdater:postupdate");
+    }
+    Trace("pf-process-debug") << "..finished" << std::endl;
+    return true;
   }
   Trace("pf-process-debug") << "..finished" << std::endl;
+  return false;
+}
+
+void ProofNodeUpdater::setDebugFreeAssumptions(
+    const std::vector<Node>& freeAssumps)
+{
+  d_freeAssumps.clear();
+  d_freeAssumps.insert(
+      d_freeAssumps.end(), freeAssumps.begin(), freeAssumps.end());
+  d_debugFreeAssumps = true;
 }
 
 }  // namespace CVC4
index 061a123104d0da32cb45b888703260e9583dfcbd..d5c0a884fa8e9e731cbe1c40b22c61972c6a73bc 100644 (file)
@@ -41,12 +41,20 @@ class ProofNodeUpdaterCallback
    * Update the proof rule application, store steps in cdp. Return true if
    * the proof changed. It can be assumed that cdp contains proofs of each
    * fact in children.
+   *
+   * If continueUpdate is set to false in this method, then the resulting
+   * proof (the proof of res in cdp) is *not* called back to update by the
+   * proof node updater, nor are its children recursed. Otherwise, by default,
+   * the proof node updater will continue updating the resulting proof and will
+   * recursively update its children. This is analogous to marking REWRITE_DONE
+   * in a rewrite response.
    */
   virtual bool update(Node res,
                       PfRule id,
                       const std::vector<Node>& children,
                       const std::vector<Node>& args,
-                      CDProof* cdp);
+                      CDProof* cdp,
+                      bool& continueUpdate);
 };
 
 /**
@@ -68,17 +76,40 @@ class ProofNodeUpdater
    */
   void process(std::shared_ptr<ProofNode> pf);
 
+  /**
+   * Set free assumptions to freeAssumps. This indicates that we expect
+   * the proof we are processing to have free assumptions that are in
+   * freeAssumps. This enables checking when this is violated, which is
+   * expensive in general. It is not recommended that this method is called
+   * by default.
+   */
+  void setDebugFreeAssumptions(const std::vector<Node>& freeAssumps);
+
  private:
   /** The proof node manager */
   ProofNodeManager* d_pnm;
   /** The callback */
   ProofNodeUpdaterCallback& d_cb;
+  /**
+   * Post-process, which performs the main post-processing technique described
+   * above.
+   */
+  void processInternal(std::shared_ptr<ProofNode> pf,
+                       const std::vector<Node>& fa);
   /**
    * Update proof node cur based on the callback. This modifies curr using
    * ProofNodeManager::updateNode based on the proof node constructed to
-   * replace it by the callback.
+   * replace it by the callback. Return true if cur was updated. If
+   * continueUpdate is updated to false, then cur is not updated further
+   * and its children are not traversed.
    */
-  void runUpdate(ProofNode* cur);
+  bool runUpdate(std::shared_ptr<ProofNode> cur,
+                 const std::vector<Node>& fa,
+                 bool& continueUpdate);
+  /** Are we debugging free assumptions? */
+  bool d_debugFreeAssumps;
+  /** The initial free assumptions */
+  std::vector<Node> d_freeAssumps;
 };
 
 }  // namespace CVC4
index 197cdd4b7b07f9013438e528681495a598522180..bb8a7c0c1d4ef6e6967f40c8afff62a9d180786f 100644 (file)
@@ -58,7 +58,8 @@ bool ProofPostprocessCallback::update(Node res,
                                       PfRule id,
                                       const std::vector<Node>& children,
                                       const std::vector<Node>& args,
-                                      CDProof* cdp)
+                                      CDProof* cdp,
+                                      bool& continueUpdate)
 {
   Trace("smt-proof-pp-debug") << "- Post process " << id << " " << children
                               << " / " << args << std::endl;
@@ -113,6 +114,16 @@ bool ProofPostprocessCallback::update(Node res,
   return !ret.isNull();
 }
 
+bool ProofPostprocessCallback::updateInternal(Node res,
+                                              PfRule id,
+                                              const std::vector<Node>& children,
+                                              const std::vector<Node>& args,
+                                              CDProof* cdp)
+{
+  bool continueUpdate = true;
+  return update(res, id, children, args, cdp, continueUpdate);
+}
+
 Node ProofPostprocessCallback::expandMacros(PfRule id,
                                             const std::vector<Node>& children,
                                             const std::vector<Node>& args,
@@ -150,7 +161,7 @@ Node ProofPostprocessCallback::expandMacros(PfRule id,
       {
         Node eq = t.eqNode(ts);
         // apply SUBS proof rule if necessary
-        if (!update(eq, PfRule::SUBS, children, sargs, cdp))
+        if (!updateInternal(eq, PfRule::SUBS, children, sargs, cdp))
         {
           // if not elimianted, add as step
           cdp->addStep(eq, PfRule::SUBS, children, sargs);
@@ -181,7 +192,7 @@ Node ProofPostprocessCallback::expandMacros(PfRule id,
     {
       Node eq = ts.eqNode(tr);
       // apply REWRITE proof rule
-      if (!update(eq, PfRule::REWRITE, {}, rargs, cdp))
+      if (!updateInternal(eq, PfRule::REWRITE, {}, rargs, cdp))
       {
         // if not elimianted, add as step
         cdp->addStep(eq, PfRule::REWRITE, {}, rargs);
index 6c75af5ce2bd05f25e06e6e4d81964ce4dbe663d..8c895275aa7bebea7b1b8da05e07ecf9766db893 100644 (file)
@@ -59,7 +59,8 @@ class ProofPostprocessCallback : public ProofNodeUpdaterCallback
               PfRule id,
               const std::vector<Node>& children,
               const std::vector<Node>& args,
-              CDProof* cdp) override;
+              CDProof* cdp,
+              bool& continueUpdate) override;
 
  private:
   /** Common constants */
@@ -96,6 +97,16 @@ class ProofPostprocessCallback : public ProofNodeUpdaterCallback
                     const std::vector<Node>& children,
                     const std::vector<Node>& args,
                     CDProof* cdp);
+  /**
+   * Update the proof rule application, called during expand macros when
+   * we wish to apply the update method. This method has the same behavior
+   * as update apart from ignoring the continueUpdate flag.
+   */
+  bool updateInternal(Node res,
+                      PfRule id,
+                      const std::vector<Node>& children,
+                      const std::vector<Node>& args,
+                      CDProof* cdp);
   /**
    * Add proof for witness form. This returns the equality t = toWitness(t)
    * and ensures that the proof of this equality has been added to cdp.