Fix transcendental secant plane lemmas (#5525)
authorGereon Kremer <gkremer@stanford.edu>
Wed, 25 Nov 2020 18:07:54 +0000 (19:07 +0100)
committerGitHub <noreply@github.com>
Wed, 25 Nov 2020 18:07:54 +0000 (12:07 -0600)
The refactoring of the transcendental solver introduced a subtle issue that lead to incorrect secant plane lemmas.
This PR fixes this issue, so that we now produce the correct lemmas again.

src/theory/arith/nl/transcendental/exponential_solver.cpp
src/theory/arith/nl/transcendental/exponential_solver.h
src/theory/arith/nl/transcendental/sine_solver.cpp
src/theory/arith/nl/transcendental/sine_solver.h
src/theory/arith/nl/transcendental/transcendental_solver.cpp
src/theory/arith/nl/transcendental/transcendental_state.cpp
src/theory/arith/nl/transcendental/transcendental_state.h

index 4275b2487677502b8ea620d5a0becfcfe6257c03..7d7d0c23c27db078b89dcc7f720e19d6d739d08c 100644 (file)
@@ -170,12 +170,11 @@ void ExponentialSolver::doTangentLemma(TNode e, TNode c, TNode poly_approx)
   d_data->d_im.addPendingArithLemma(lem, InferenceId::NL_T_TANGENT, nullptr, true);
 }
 
-void ExponentialSolver::doSecantLemmas(TNode e,
-                                       TNode c,
-                                       TNode poly_approx,
-                                       unsigned d)
+void ExponentialSolver::doSecantLemmas(
+    TNode e, TNode poly_approx, TNode c, TNode poly_approx_c, unsigned d)
 {
-  d_data->doSecantLemmas(getSecantBounds(e, c, d), c, poly_approx, e, d, 1);
+  d_data->doSecantLemmas(
+      getSecantBounds(e, c, d), poly_approx, c, poly_approx_c, e, d, 1);
 }
 
 std::pair<Node, Node> ExponentialSolver::getSecantBounds(TNode e,
index f757e589221ab7a9e7923b4191ad41fa2d492d8e..b20c23e561380a6debc2000199b4e9a1cd5b7822 100644 (file)
@@ -87,7 +87,8 @@ class ExponentialSolver
   void doTangentLemma(TNode e, TNode c, TNode poly_approx);
 
   /** Sent secant lemmas around c for e */
-  void doSecantLemmas(TNode e, TNode c, TNode poly_approx, unsigned d);
+  void doSecantLemmas(
+      TNode e, TNode poly_approx, TNode c, TNode poly_approx_c, unsigned d);
 
  private:
   /** Generate bounds for secant lemmas */
index 012b8e7ec0cd02fea971407288171eb8fe857943..31fd475034bde11f4e6b1ff3de67ee8af12257b2 100644 (file)
@@ -289,12 +289,17 @@ void SineSolver::doTangentLemma(TNode e, TNode c, TNode poly_approx, int region)
   d_data->d_im.addPendingArithLemma(lem, InferenceId::NL_T_TANGENT, nullptr, true);
 }
 
-void SineSolver::doSecantLemmas(
-    TNode e, TNode c, TNode poly_approx, unsigned d, int region)
+void SineSolver::doSecantLemmas(TNode e,
+                                TNode poly_approx,
+                                TNode c,
+                                TNode poly_approx_c,
+                                unsigned d,
+                                int region)
 {
   d_data->doSecantLemmas(getSecantBounds(e, c, d, region),
-                         c,
                          poly_approx,
+                         c,
+                         poly_approx_c,
                          e,
                          d,
                          regionToConcavity(region));
index 394c2d927409e0eee3c88e863dc90b39b83f5e05..9f9102a53ccff0c6047d69a8bde2e90c9ce21c1e 100644 (file)
@@ -88,8 +88,12 @@ class SineSolver
   void doTangentLemma(TNode e, TNode c, TNode poly_approx, int region);
 
   /** Sent secant lemmas around c for e */
-  void doSecantLemmas(
-      TNode e, TNode c, TNode poly_approx, unsigned d, int region);
+  void doSecantLemmas(TNode e,
+                      TNode poly_approx,
+                      TNode c,
+                      TNode poly_approx_c,
+                      unsigned d,
+                      int region);
 
  private:
   std::pair<Node, Node> getSecantBounds(TNode e,
index 1f76cd8339c74324487a65857d656ab95b7c69fc..8eb69e50bce11a963f3e0d23c2e785cb095ee555 100644 (file)
@@ -362,11 +362,11 @@ bool TranscendentalSolver::checkTfTangentPlanesFun(Node tf, unsigned d)
   {
     if (k == EXPONENTIAL)
     {
-      d_expSlv.doSecantLemmas(tf, c, poly_approx_c, d);
+      d_expSlv.doSecantLemmas(tf, poly_approx, c, poly_approx_c, d);
     }
     else if (k == Kind::SINE)
     {
-      d_sineSlv.doSecantLemmas(tf, c, poly_approx_c, d, region);
+      d_sineSlv.doSecantLemmas(tf, poly_approx, c, poly_approx_c, d, region);
     }
   }
   return true;
index b6882d606c7ea43dbd00cd04fd941783b4d2162b..41ed2c53d87f7527c69c942f4522a0e27358ccba 100644 (file)
@@ -294,12 +294,9 @@ std::pair<Node, Node> TranscendentalState::getClosestSecantPoints(TNode e,
 }
 
 Node TranscendentalState::mkSecantPlane(
-    TNode arg, TNode b, TNode c, TNode approx, TNode approx_c)
+    TNode arg, TNode b, TNode c, TNode approx_b, TNode approx_c)
 {
   NodeManager* nm = NodeManager::currentNM();
-  // Figure 3 : P(l), P(u), for s = 0,1
-  Node approx_b =
-      Rewriter::rewrite(approx.substitute(d_taylor.getTaylorVariable(), b));
   // Figure 3: S_l( x ), S_u( x ) for s = 0,1
   Node rcoeff_n = Rewriter::rewrite(nm->mkNode(Kind::MINUS, b, c));
   Assert(rcoeff_n.isConst());
@@ -345,18 +342,26 @@ NlLemma TranscendentalState::mkSecantLemma(
 }
 
 void TranscendentalState::doSecantLemmas(const std::pair<Node, Node>& bounds,
+                                         TNode poly_approx,
                                          TNode c,
                                          TNode approx_c,
                                          TNode tf,
                                          unsigned d,
                                          int concavity)
 {
+  Trace("nl-ext-tftp-debug2") << "...secant bounds are : " << bounds.first
+                              << " ... " << bounds.second << std::endl;
   // take the model value of l or u (since may contain PI)
   // Make secant from bounds.first to c
   Node lval = d_model.computeAbstractModelValue(bounds.first);
+  Trace("nl-ext-tftp-debug2") << "...model value of bound " << bounds.first
+                              << " is " << lval << std::endl;
   if (lval != c)
   {
-    Node splane = mkSecantPlane(tf[0], lval, c, bounds.first, approx_c);
+    // Figure 3 : P(l), P(u), for s = 0
+    Node approx_l = Rewriter::rewrite(
+        poly_approx.substitute(d_taylor.getTaylorVariable(), lval));
+    Node splane = mkSecantPlane(tf[0], lval, c, approx_l, approx_c);
     NlLemma nlem = mkSecantLemma(lval, c, concavity, tf, splane);
     // The side effect says that if lem is added, then we should add the
     // secant point c for (tf,d).
@@ -366,9 +371,14 @@ void TranscendentalState::doSecantLemmas(const std::pair<Node, Node>& bounds,
 
   // Make secant from c to bounds.second
   Node uval = d_model.computeAbstractModelValue(bounds.second);
+  Trace("nl-ext-tftp-debug2") << "...model value of bound " << bounds.second
+                              << " is " << uval << std::endl;
   if (c != uval)
   {
-    Node splane = mkSecantPlane(tf[0], c, uval, approx_c, bounds.second);
+    // Figure 3 : P(l), P(u), for s = 1
+    Node approx_u = Rewriter::rewrite(
+        poly_approx.substitute(d_taylor.getTaylorVariable(), uval));
+    Node splane = mkSecantPlane(tf[0], c, uval, approx_c, approx_u);
     NlLemma nlem = mkSecantLemma(c, uval, concavity, tf, splane);
     // The side effect says that if lem is added, then we should add the
     // secant point c for (tf,d).
index 2cf6400a3ce4fe4cc1096f1ceb00965f2bcba40b..0a4e46eca0e19a28f5057d1dc91f0c63708d5b4a 100644 (file)
@@ -89,6 +89,7 @@ struct TranscendentalState
   /**
    * Construct and send secant lemmas (if appropriate)
    * @param bounds Secant bounds
+   * @param poly_approx Polynomial approximation
    * @param c Current point
    * @param approx_c Approximation for c
    * @param tf Current transcendental term
@@ -96,118 +97,118 @@ struct TranscendentalState
    * @param concavity Concavity in region of c
    */
   void doSecantLemmas(const std::pair<Node, Node>& bounds,
+                      TNode poly_approx,
                       TNode c,
                       TNode approx_c,
                       TNode tf,
                       unsigned d,
-                      int concavity)
-    ;
-
-    Node d_true;
-    Node d_false;
-    Node d_zero;
-    Node d_one;
-    Node d_neg_one;
-
-    /** The inference manager that we push conflicts and lemmas to. */
-    InferenceManager& d_im;
-    /** Reference to the non-linear model object */
-    NlModel& d_model;
-    /** Utility to compute taylor approximations */
-    TaylorGenerator d_taylor;
-
-    /**
-     * Some transcendental functions f(t) are "purified", e.g. we add
-     * t = y ^ f(t) = f(y) where y is a fresh variable. Those that are not
-     * purified we call "master terms".
-     *
-     * The maps below maintain a master/slave relationship over
-     * transcendental functions (SINE, EXPONENTIAL, PI), where above
-     * f(y) is the master of itself and of f(t).
-     *
-     * This is used for ensuring that the argument y of SINE we process is on
-     * the interval [-pi .. pi], and that exponentials are not applied to
-     * arguments that contain transcendental functions.
-     */
-    std::map<Node, Node> d_trMaster;
-    std::map<Node, std::unordered_set<Node, NodeHashFunction>> d_trSlaves;
-
-    /** concavity region for transcendental functions
-     *
-     * This stores an integer that identifies an interval in
-     * which the current model value for an argument of an
-     * application of a transcendental function resides.
-     *
-     * For exp( x ):
-     *   region #1 is -infty < x < infty
-     * For sin( x ):
-     *   region #0 is pi < x < infty (this is an invalid region)
-     *   region #1 is pi/2 < x <= pi
-     *   region #2 is 0 < x <= pi/2
-     *   region #3 is -pi/2 < x <= 0
-     *   region #4 is -pi < x <= -pi/2
-     *   region #5 is -infty < x <= -pi (this is an invalid region)
-     * All regions not listed above, as well as regions 0 and 5
-     * for SINE are "invalid". We only process applications
-     * of transcendental functions whose arguments have model
-     * values that reside in valid regions.
-     */
-    std::unordered_map<Node, int, NodeHashFunction> d_tf_region;
-    /**
-     * Maps representives of a congruence class to the members of that class.
-     *
-     * In detail, a congruence class is a set of terms of the form
-     *   { f(t1), ..., f(tn) }
-     * such that t1 = ... = tn in the current context. We choose an arbitrary
-     * term among these to be the repesentative of this congruence class.
-     *
-     * Moreover, notice we compute congruence classes only over terms that
-     * are transcendental function applications that are "master terms",
-     * see d_trMaster/d_trSlave.
-     */
-    std::map<Node, std::vector<Node>> d_funcCongClass;
-    /**
-     * A list of all functions for each kind in { EXPONENTIAL, SINE, POW, PI }
-     * that are representives of their congruence class.
-     */
-    std::map<Kind, std::vector<Node>> d_funcMap;
-
-    /** secant points (sorted list) for transcendental functions
-     *
-     * This is used for tangent plane refinements for
-     * transcendental functions. This is the set
-     * "get-previous-secant-points" in "Satisfiability
-     * Modulo Transcendental Functions via Incremental
-     * Linearization" by Cimatti et al., CADE 2017, for
-     * each transcendental function application. We store this set for each
-     * Taylor degree.
-     */
-    std::unordered_map<Node,
-                       std::map<unsigned, std::vector<Node>>,
-                       NodeHashFunction>
-        d_secant_points;
-
-    /** PI
-     *
-     * Note that PI is a (symbolic, non-constant) nullary operator. This is
-     * because its value cannot be computed exactly. We constraint PI to
-     * concrete lower and upper bounds stored in d_pi_bound below.
-     */
-    Node d_pi;
-    /** PI/2 */
-    Node d_pi_2;
-    /** -PI/2 */
-    Node d_pi_neg_2;
-    /** -PI */
-    Node d_pi_neg;
-    /** the concrete lower and upper bounds for PI */
-    Node d_pi_bound[2];
-  };
+                      int concavity);
+
+  Node d_true;
+  Node d_false;
+  Node d_zero;
+  Node d_one;
+  Node d_neg_one;
+
+  /** The inference manager that we push conflicts and lemmas to. */
+  InferenceManager& d_im;
+  /** Reference to the non-linear model object */
+  NlModel& d_model;
+  /** Utility to compute taylor approximations */
+  TaylorGenerator d_taylor;
+
+  /**
+   * Some transcendental functions f(t) are "purified", e.g. we add
+   * t = y ^ f(t) = f(y) where y is a fresh variable. Those that are not
+   * purified we call "master terms".
+   *
+   * The maps below maintain a master/slave relationship over
+   * transcendental functions (SINE, EXPONENTIAL, PI), where above
+   * f(y) is the master of itself and of f(t).
+   *
+   * This is used for ensuring that the argument y of SINE we process is on
+   * the interval [-pi .. pi], and that exponentials are not applied to
+   * arguments that contain transcendental functions.
+   */
+  std::map<Node, Node> d_trMaster;
+  std::map<Node, std::unordered_set<Node, NodeHashFunction>> d_trSlaves;
+
+  /** concavity region for transcendental functions
+   *
+   * This stores an integer that identifies an interval in
+   * which the current model value for an argument of an
+   * application of a transcendental function resides.
+   *
+   * For exp( x ):
+   *   region #1 is -infty < x < infty
+   * For sin( x ):
+   *   region #0 is pi < x < infty (this is an invalid region)
+   *   region #1 is pi/2 < x <= pi
+   *   region #2 is 0 < x <= pi/2
+   *   region #3 is -pi/2 < x <= 0
+   *   region #4 is -pi < x <= -pi/2
+   *   region #5 is -infty < x <= -pi (this is an invalid region)
+   * All regions not listed above, as well as regions 0 and 5
+   * for SINE are "invalid". We only process applications
+   * of transcendental functions whose arguments have model
+   * values that reside in valid regions.
+   */
+  std::unordered_map<Node, int, NodeHashFunction> d_tf_region;
+  /**
+   * Maps representives of a congruence class to the members of that class.
+   *
+   * In detail, a congruence class is a set of terms of the form
+   *   { f(t1), ..., f(tn) }
+   * such that t1 = ... = tn in the current context. We choose an arbitrary
+   * term among these to be the repesentative of this congruence class.
+   *
+   * Moreover, notice we compute congruence classes only over terms that
+   * are transcendental function applications that are "master terms",
+   * see d_trMaster/d_trSlave.
+   */
+  std::map<Node, std::vector<Node>> d_funcCongClass;
+  /**
+   * A list of all functions for each kind in { EXPONENTIAL, SINE, POW, PI }
+   * that are representives of their congruence class.
+   */
+  std::map<Kind, std::vector<Node>> d_funcMap;
+
+  /** secant points (sorted list) for transcendental functions
+   *
+   * This is used for tangent plane refinements for
+   * transcendental functions. This is the set
+   * "get-previous-secant-points" in "Satisfiability
+   * Modulo Transcendental Functions via Incremental
+   * Linearization" by Cimatti et al., CADE 2017, for
+   * each transcendental function application. We store this set for each
+   * Taylor degree.
+   */
+  std::unordered_map<Node,
+                     std::map<unsigned, std::vector<Node>>,
+                     NodeHashFunction>
+      d_secant_points;
+
+  /** PI
+   *
+   * Note that PI is a (symbolic, non-constant) nullary operator. This is
+   * because its value cannot be computed exactly. We constraint PI to
+   * concrete lower and upper bounds stored in d_pi_bound below.
+   */
+  Node d_pi;
+  /** PI/2 */
+  Node d_pi_2;
+  /** -PI/2 */
+  Node d_pi_neg_2;
+  /** -PI */
+  Node d_pi_neg;
+  /** the concrete lower and upper bounds for PI */
+  Node d_pi_bound[2];
+};
 
-}  // namespace transcendental
 }  // namespace transcendental
 }  // namespace nl
 }  // namespace arith
 }  // namespace theory
+}  // namespace CVC4
 
 #endif /* CVC4__THEORY__ARITH__NL__TRANSCENDENTAL__TRANSCENDENTAL_STATE_H */