Add API unit tests for statistics (#8341)
authorGereon Kremer <gkremer@cs.stanford.edu>
Wed, 23 Mar 2022 22:59:23 +0000 (23:59 +0100)
committerGitHub <noreply@github.com>
Wed, 23 Mar 2022 22:59:23 +0000 (22:59 +0000)
This PR adds some unit tests that cover getStatistics() and the api::Stat class.

docs/statistics.rst
examples/api/java/Statistics.java
src/api/java/io/github/cvc5/api/Statistics.java
src/api/java/jni/statistics.cpp
src/options/mkoptions.py
test/unit/api/cpp/solver_black.cpp
test/unit/api/java/SolverTest.java

index ea4410df49e161dbdd60919326e24fae7efec43f..263104978b5f94058fe0ca83cf23ef051640a3c3 100644 (file)
@@ -41,7 +41,7 @@ A statistic value can be any of the following types:
 
 Printing statistics on the command line looks like this:
 
-.. run-command:: bin/cvc5 --stats ../test/regress/regress0/auflia/bug336.smt2
+.. run-command:: bin/cvc5 --stats ../test/regress/cli/regress0/auflia/bug336.smt2
 
 Public statistics include some general information about the input file
 (``driver::filename`` and ``api::*``), the overall runtime (``global::totalTime``)
index 635be65f6b6ea2780a7354f2363c5792e12f2621..a09127fe833f160ebddb44c9b46d8901dd070123 100644 (file)
@@ -37,25 +37,25 @@ public class Statistics
       System.out.println("Long version:");
 
       // long version
-      for (Pair<String, Stat> pair : stats)
+      for (Map.Entry<String, Stat> pair : stats)
       {
-        Stat stat = pair.second;
+        Stat stat = pair.getValue();
         if (stat.isInt())
         {
-          System.out.println(pair.first + " = " + stat.getInt());
+          System.out.println(pair.getKey() + " = " + stat.getInt());
         }
         else if (stat.isDouble())
         {
-          System.out.println(pair.first + " = " + stat.getDouble());
+          System.out.println(pair.getKey() + " = " + stat.getDouble());
         }
         else if (stat.isString())
         {
-          System.out.println(pair.first + " = " + stat.getString());
+          System.out.println(pair.getKey() + " = " + stat.getString());
         }
         else if (stat.isHistogram())
         {
           System.out.println("-------------------------------------------------------");
-          System.out.println(pair.first + " : Map");
+          System.out.println(pair.getKey() + " : Map");
           for (Map.Entry<String, Long> entry : stat.getHistogram().entrySet())
           {
             System.out.println(entry.getKey() + " = " + entry.getValue());
index 0c64370c05ce053cb4f1c8ac3cd94a4ff5c69143..df7a82c5866ab2e22cbd347548b8ae6d8e54f152 100644 (file)
@@ -17,8 +17,10 @@ package io.github.cvc5.api;
 
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.Map;
+import java.util.AbstractMap;
 
-public class Statistics extends AbstractPointer implements Iterable<Pair<String, Stat>>
+public class Statistics extends AbstractPointer implements Iterable<Map.Entry<String, Stat>>
 {
   // region construction and destruction
   Statistics(Solver solver, long pointer)
@@ -63,6 +65,7 @@ public class Statistics extends AbstractPointer implements Iterable<Pair<String,
    * @param defaulted If set to true, defaulted statistics are shown as well.
    */
 
+  private native long getIteratorOpts(long pointer, boolean internal, boolean defaulted);
   private native long getIterator(long pointer);
 
   private native boolean hasNext(long pointer, long iteratorPointer);
@@ -74,10 +77,14 @@ public class Statistics extends AbstractPointer implements Iterable<Pair<String,
 
   private native void deleteIteratorPointer(long iteratorPointer);
 
-  public class ConstIterator implements Iterator<Pair<String, Stat>>
+  public class ConstIterator implements Iterator<Map.Entry<String, Stat>>
   {
     private long iteratorPointer = 0;
 
+    public ConstIterator(boolean internal, boolean defaulted)
+    {
+      iteratorPointer = getIteratorOpts(pointer, internal, defaulted);
+    }
     public ConstIterator()
     {
       iteratorPointer = getIterator(pointer);
@@ -88,14 +95,14 @@ public class Statistics extends AbstractPointer implements Iterable<Pair<String,
       return Statistics.this.hasNext(pointer, iteratorPointer);
     }
 
-    @Override public Pair<String, Stat> next()
+    @Override public Map.Entry<String, Stat> next()
     {
       try
       {
         Pair<String, Long> pair = Statistics.this.getNext(pointer, iteratorPointer);
         Stat stat = new Stat(solver, pair.second);
         this.iteratorPointer = Statistics.this.increment(pointer, iteratorPointer);
-        return new Pair<>(pair.first, stat);
+        return new AbstractMap.SimpleImmutableEntry(pair.first, stat);
       }
       catch (CVC5ApiException e)
       {
@@ -109,7 +116,11 @@ public class Statistics extends AbstractPointer implements Iterable<Pair<String,
     }
   }
 
-  @Override public Iterator<Pair<String, Stat>> iterator()
+  public ConstIterator iterator(boolean internal, boolean defaulted)
+  {
+    return new ConstIterator(internal, defaulted);
+  }
+  @Override public ConstIterator iterator()
   {
     return new ConstIterator();
   }
index dfea8bf9ddb14470a11f17a405c746480f663d14..f76fd8e4609e133c6885a6278c12b1d123819f19 100644 (file)
@@ -69,6 +69,22 @@ JNIEXPORT jlong JNICALL Java_io_github_cvc5_api_Statistics_get(JNIEnv* env,
   CVC5_JAVA_API_TRY_CATCH_END_RETURN(env, 0);
 }
 
+/*
+ * Class:     io_github_cvc5_api_Statistics
+ * Method:    getIterator
+ * Signature: (JZZ)J
+ */
+JNIEXPORT jlong JNICALL Java_io_github_cvc5_api_Statistics_getIteratorOpts(
+    JNIEnv* env, jobject, jlong pointer, jboolean internal, jboolean defaulted)
+{
+  CVC5_JAVA_API_TRY_CATCH_BEGIN;
+  Statistics* current = reinterpret_cast<Statistics*>(pointer);
+  Statistics::iterator* it =
+      new Statistics::iterator(current->begin(internal, defaulted));
+  return reinterpret_cast<jlong>(it);
+  CVC5_JAVA_API_TRY_CATCH_END_RETURN(env, 0);
+}
+
 /*
  * Class:     io_github_cvc5_api_Statistics
  * Method:    getIterator
@@ -80,7 +96,7 @@ JNIEXPORT jlong JNICALL Java_io_github_cvc5_api_Statistics_getIterator(
   CVC5_JAVA_API_TRY_CATCH_BEGIN;
   Statistics* current = reinterpret_cast<Statistics*>(pointer);
   Statistics::iterator* it =
-      new Statistics::iterator(current->begin(true, true));
+      new Statistics::iterator(current->begin());
   return reinterpret_cast<jlong>(it);
   CVC5_JAVA_API_TRY_CATCH_END_RETURN(env, 0);
 }
index de76dbe52d7f9f10d33d2be24c2876e7af59d461..03e190d960ee6e51c4a4be883d2df5bb7771401f 100644 (file)
@@ -818,7 +818,7 @@ def generate_sphinx_output_tags(modules, src_dir, build_dir):
         res.append(info['description'])
         if 'example-file' in info:
             res.append('')
-            res.append('.. command-output:: bin/cvc5 -o {} ../test/regress/{}'.format(info['name'], info['example-file']))
+            res.append('.. command-output:: bin/cvc5 -o {} ../test/regress/cli/{}'.format(info['name'], info['example-file']))
             res.append('  :cwd: {}'.format(cwd))
         res.append('')
         res.append('')
index 7c28fde12d80ff7d23e1ab1030c4d23cfc52cde9..7e6c113eb8344e4f5be54179a2bdc13aee8884f7 100644 (file)
@@ -14,6 +14,7 @@
  */
 
 #include <algorithm>
+#include <cmath>
 
 #include "base/output.h"
 #include "test_api.h"
@@ -1580,6 +1581,77 @@ TEST_F(TestApiBlackSolver, getOptionInfo)
   }
 }
 
+TEST_F(TestApiBlackSolver, getStatistics)
+{
+  // do some array reasoning to make sure we have a double statistics
+  {
+    Sort s1 = d_solver.getIntegerSort();
+    Sort s2 = d_solver.mkArraySort(s1, s1);
+    Term t1 = d_solver.mkConst(s1, "i");
+    Term t2 = d_solver.mkVar(s2, "a");
+    Term t3 = d_solver.mkTerm(Kind::SELECT, {t2, t1});
+    d_solver.checkSat();
+  }
+  cvc5::api::Statistics stats = d_solver.getStatistics();
+  {
+    std::stringstream ss;
+    ss << stats;
+  }
+  {
+    auto s = stats.get("global::totalTime");
+    EXPECT_FALSE(s.isInternal());
+    EXPECT_FALSE(s.isDefault());
+    EXPECT_TRUE(s.isString());
+    std::string time = s.getString();
+    EXPECT_TRUE(time.rfind("ms") == time.size() - 2);  // ends with "ms"
+    EXPECT_FALSE(s.isDouble());
+    s = stats.get("resource::resourceUnitsUsed");
+    EXPECT_TRUE(s.isInternal());
+    EXPECT_FALSE(s.isDefault());
+    EXPECT_TRUE(s.isInt());
+    EXPECT_TRUE(s.getInt() >= 0);
+  }
+  for (const auto& s: stats)
+  {
+    EXPECT_FALSE(s.first.empty());
+  }
+  for (auto it = stats.begin(true, true); it != stats.end(); ++it)
+  {
+    {
+      auto tmp1 = it, tmp2 = it;
+      ++tmp1;
+      tmp2++;
+      EXPECT_EQ(tmp1, tmp2);
+      --tmp1;
+      tmp2--;
+      EXPECT_EQ(tmp1, tmp2);
+      EXPECT_EQ(tmp1, it);
+      EXPECT_EQ(it, tmp2);
+    }
+    const auto& s = *it;
+    // check some basic utility methods
+    EXPECT_TRUE(!(it == stats.end()));
+    EXPECT_EQ(s.first, it->first);
+    if (s.first == "api::CONSTANT")
+    {
+      EXPECT_FALSE(s.second.isInternal());
+      EXPECT_FALSE(s.second.isDefault());
+      EXPECT_TRUE(s.second.isHistogram());
+      auto hist = s.second.getHistogram();
+      EXPECT_FALSE(hist.empty());
+      std::stringstream ss;
+      ss << s.second;
+      EXPECT_EQ(ss.str(), "{ integer type: 1 }");
+    }
+    else if (s.first == "theory::arrays::avgIndexListLength")
+    {
+      EXPECT_TRUE(s.second.isInternal());
+      EXPECT_TRUE(s.second.isDouble());
+      EXPECT_TRUE(std::isnan(s.second.getDouble()));
+    }
+  }
+}
+
 TEST_F(TestApiBlackSolver, getUnsatAssumptions1)
 {
   d_solver.setOption("incremental", "false");
index ba93ed102d2f8f4a8122e20ec6602a3206e92a0a..cc94600f5c99f03f930a65457d4fa947b10081a1 100644 (file)
@@ -1552,6 +1552,52 @@ class SolverTest
     assertAll(assertions);
   }
 
+  @Test void getStatistics()
+  {
+    // do some array reasoning to make sure we have a double statistics
+    {
+      Sort s1 = d_solver.getIntegerSort();
+      Sort s2 = d_solver.mkArraySort(s1, s1);
+      Term t1 = d_solver.mkConst(s1, "i");
+      Term t2 = d_solver.mkVar(s2, "a");
+      Term t3 = d_solver.mkTerm(Kind.SELECT, t2, t1);
+      d_solver.checkSat();
+    }
+    Statistics stats = d_solver.getStatistics();
+    stats.toString();
+    {
+      Stat s = stats.get("global::totalTime");
+      assertFalse(s.isInternal());
+      assertFalse(s.isDefault());
+      assertTrue(s.isString());
+      assertTrue(s.getString().endsWith("ms"));
+      s = stats.get("resource::resourceUnitsUsed");
+      assertTrue(s.isInternal());
+      assertFalse(s.isDefault());
+      assertTrue(s.isInt());
+      assertTrue(s.getInt() >= 0);
+    }
+    for (Map.Entry<String, Stat> s : stats) {}
+    for (Statistics.ConstIterator it = stats.iterator(true, true); it.hasNext();)
+    {
+      Map.Entry<String, Stat> elem = it.next();
+      if (elem.getKey() == "api::CONSTANT")
+      {
+        assertFalse(elem.getValue().isInternal());
+        assertFalse(elem.getValue().isDefault());
+        assertTrue(elem.getValue().isHistogram());
+        Map<String, Long> hist = elem.getValue().getHistogram();
+        assertFalse(hist.isEmpty());
+        assertEquals(elem.getValue().toString(), "{ integer type: 1 }");
+      }
+      else if (elem.getKey() == "theory::arrays::avgIndexListLength")
+      {
+        assertTrue(elem.getValue().isInternal());
+        assertTrue(elem.getValue().isDefault());
+      }
+    }
+  }
+
   @Test void getUnsatAssumptions1()
   {
     d_solver.setOption("incremental", "false");