using Nemerle using Nemerle.Utility using Nemerle.Logging using Nemerle.Collections using Nemerle.Imperative using Nemerle.Profiling using Nemerle.Text using System using System.Console using System.Text.RegularExpressions set namespace Fx7 class Hash [K, V] : Hashtable[K, V] where V : new () private check(k : K) : void when (!Contains(k)) base[k] = V() public new Item [k : K] : V get check(k) base[k] set check(k) base[k] = value /* STATUS Fairly stable. NOTES - I rely on the grinder to flatten \/ and /\ and to remove duplicate children of those operators. TODO - Check the effect of transforming an assert into an assume on the query. - Improve speed. */ public class Pruner core : Core pool : Term.Pool star : Term False : Term True : Term Old : Hashtable[Term, Term] = Hashtable() // maps new to old terms New : Hashtable[Term, Term] = Hashtable() // maps old to new terms oldParents : Hash[Term, Hash[list[string], int]] = Hash() newParents : Hash[Term, Hash[list[string], int]] = Hash() oldIds : Hashtable[Term, int] = Hashtable() newIds : Hashtable[Term, int] = Hashtable() sortedTerm : Hashtable[Term, Term] = Hashtable() expliesCache : Hashtable[Term, Hashtable[Term, bool]] = Hashtable() static quantVarPrefix : string = "qVar" static skolemFunctionPrefix : string = "$@fn" public this (c : Core) core = c pool = c.TermPool star = pool.Get("*", []) False = pool.Get("false", []) True = pool.Get("true", []) regex : Regex = Regex (@".*[:_]\d+\.\d.*") locationCache : Hashtable [Term, bool] = Hashtable () Substitute(t : Term, s : Hashtable[Term, Term]) : Term if (s.Contains(t)) s[t] else t.MapChildren(Substitute(_, s)) // Renames the quantified variables in |t| with "qv
", // where
is the depth level and "(forall x y" is // interpreted as "(forall x (forall y". (roughly) [Profile]\ RenameQuantifiedVar(t : Term) : Term def cache = Hashtable () def RecRQV(d, m, t) if (d == 0 && cache.Contains(t)) cache[t] else def res = match (t.Name, t.Children) | _ when t.Arity == 0 && m.Contains(t.Id) => m.Get(t.Id) | ("forall", vars::_) \ | ("exists", vars::_) => def addRename(v, (m, d)) (m.Replace(v.Id, pool.Get($"$quantVarPrefix$d", [])), d + 1) def (m, d) = vars.Children.Fold((m, d), addRename) t.MapChildren(RecRQV(d, m, _)) | (name, _ :: _) when name.StartsWith(skolemFunctionPrefix) => pool.Get ("%skol", pool.Get (name, []) :: t.Children) | _ => t.MapChildren(RecRQV(d, m, _)) when (d == 0) cache[t] = res res RecRQV(0, IntTree.Empty(), t) [Profile]\ static IsVariable(a : Term) : bool // TODO: perhaps add here classes from the java API? | V ("true") | V ("false") | V ("null") => false | V (name) => !(LinearTheory.IsNumber(name)) &&\ !name.StartsWith(quantVarPrefix) | _ => false // Check if |a| is implied by |b| [Profile]\ Explies(a : Term, b : Term) : bool ++mems if (a === b) true else ++mems match (a.Name) | "and" => mems += a.Arity a.Children.ForAll(Explies(_, b)) | "or" => mems += a.Arity a.Children.Exists(Explies(_, b)) | _ => false MkAndOr(a : list [Term], one : Term, _zero : Term, name : string) : Term if (a.Exists(_ === one)) one else pool.Get(name, a) /* for debug, makes it easier to compare a.smt to b.smt match (a.Filter(_ !== zero)) | [] => zero | [t] => t | lst => pool.Get(name, lst) */ MkAnd(a : list [Term]) : Term MkAndOr(a, False, True, "and") MkOr(a : list [Term]) : Term MkAndOr(a, True, False, "or") [Profile]\ RemoveDuplicates(tl : list [Term]) : list [Term] def h = Hashtable() def f(t, r) if (h.Contains(t)) log(PRUNE, "remove duplicate") r else h[t] = t t :: r tl.Fold([], f).Rev() mutable mems : int = 0 [Profile] \ // a is disjunction of conjuncts (DNF) Flatten (a : list[list[Term]]) : list[list[Term]] def flattenOr(x : list [Term]) | [E("or", c)] => c.Map(x => [x]) | _ => [x] def flattenAnd(x : Term) | E("and", c) => c | _ => [x] def a = a.Fold([], (x, y) => flattenOr(x) + y) a.Map(x => x.Fold([], (y, z) => flattenAnd(y) + z)) // Returns a term equisatisfiable to |b| assuming that |a| is unsat [Profile]\ Prune(a : Term, b : Term) : Term mems = 0 def RecPrune(a, b) def smems = mems _ = smems log(PRUNE, "{") log(PRUNE, $"old=$a") log(PRUNE, $"new=$b") def r = RecPruneNoLog(a, b) log(PRUNE, $"prn=$r}") log(PRUNE, $"mems=$(mems-smems)") log(PRUNE, $"L1xL2 = $(a.Length * a.Fold(0,(t,s)=>t.Length+s))") r and RecPruneNoLog(a : list [list [Term]], b : Term) ++mems if (a is []) b else def a = Flatten(a) match (b.Name) | "and" => // commonAB = terms that appear in both A and B // prunedA = A - commonA //log (TEMP, $ "len: $(b.Arity) $(a.Length) $(a.Fold(0,(l,a) => a+l.Length))") //def time = Core.read_tsc() // TODO: handle DISTINCT specially // This part must be linear time, otherwise the speed sucks def allB = Hashtable() mems += b.Arity b.IterChildren(bt => allB[bt] = bt) def commonAB = Hashtable() def nestIter(lst, f) lst.Iter(l => l.Iter(f)) def recCommon(t) ++mems when (allB.Contains(t)) ++mems commonAB[t] = t nestIter(a, recCommon) def prune(l, s) l.Filter(x => !s.Contains(x)) mems += a.Length + a.Fold(0, (t, s) => t.Length + s) def prunedA = a.Filter(x => x.Exists(y => commonAB.Contains(y))) mems += prunedA.Length + prunedA.Fold(0, (t, s) => t.Length + s) def prunedA = prunedA.Map(prune(_, commonAB)) //def time = (Core.read_tsc() - time) / 1000 //log (TEMP, $ "done: -> $(prunedA.Fold(0,(l,a) => a+l.Length)) $time ") ++mems if (prunedA.Contains([])) log(PRUNE, $"throw (True) $b") False else def filter(bc) ++mems if (commonAB.Contains(bc)) bc else RecPrune(prunedA, bc) mems += b.Arity + b.Arity MkAnd(RemoveDuplicates(b.Children.Map(filter))) | "or" => mems += b.Arity MkOr(b.Children.Map(RecPrune(a, _))) | "distinct" => log(PRUNE, $"b=$b a=$a") def allB = Hashtable() b.IterChildren(bc => allB[bc] = bc) def expliesDistinct(at : list[Term]) | [E("distinct", ac)] => ac.ForAll(allB.Contains(_)) | _ => false if (a.Exists(expliesDistinct)) log(PRUNE, $"throw (Distinct) $b") False else b | _ => mems += a.Length + a.Fold(0, (t, s) => t.Length + s) if (a.Exists(x => x.ForAll(Explies(_, b)))) log(PRUNE, $"throw (Explies) $b") False else b log(PRUNE, "start the work") def a = RenameQuantifiedVar(a) def b = RenameQuantifiedVar(b) CollectVariables(a, b) Unify() def a = Substitute(a, New) log(PRUNE, $"same = $(a === b)") def a = SortTerm(a) def b = SortTerm(b) //System.IO.File.WriteAllText("q1-ren.smt", a.ToPrettyString()) //System.IO.File.WriteAllText("q2-ren.smt", b.ToPrettyString()) mutable newDist = [] def collectDistinct(t) match (t.Name) | "and" | "or" | "not" | "forall" | "exists" => t.IterChildren(collectDistinct) | "distinct" => newDist ::= t | _ => {} collectDistinct(b) def strengthenDist(t) match (t.Name) | "and" | "or" | "not" | "forall" | "exists" => t.MapChildren(strengthenDist) | "distinct" => log (PRUNE, $"process: $t") mutable best = null mutable best_add = 0 foreach (t' in newDist) def nd = Hashtable() t'.IterChildren(t => nd[t] = t) when (t.Children.ForAll(t => nd.Contains(t)) && (best == null || nd.Count - t.Arity < best_add)) best = t' best_add = nd.Count - t.Arity if (best != null && best !== t) log (PRUNE, $ "strengthen dist: $t -> $best") best else t | _ => t def a = strengthenDist(a) System.IO.File.WriteAllText("a.smt", b.ToPrettyString()) def b = RecPrune([[a]], b) System.IO.File.WriteAllText("b.smt", b.ToPrettyString()) log(PRUNE, $"total_mems=$mems") b static sizeCache : Hashtable[Term, int] = Hashtable() static TermSize(t : Term) : int use_cache(sizeCache[t]) t.Children.Fold(1, (t, s) => TermSize(t) + s) // Uses the element comparator |elCmp| to make a list comparator public static Compare['a,'b](x : list ['a], y : list ['b], elCmp : ('a * 'b) -> int) : int match (x, y) | ([], []) => 0 | ([], _::_) => -1 | (_::_, []) => +1 | (xh::xt, yh::yt) => def h = elCmp(xh, yh) if (h == 0) Compare(xt, yt, elCmp) else h // Prepares for `similarity' heuristic // TODO: perhaps memoize this to make it faster. [Profile]\ CollectVariables(a : Term, b : Term) : void mutable termId = 0; def RecCV(t, tp, parents, ids) mutable pos = match (t.Name) | "and" | "or" | "distinct" | "=" | "+" | "*" => -1 | _ => 0 foreach (c in t.Children) def name = if (pos == -1) t.Name else ++pos $ "t.Name/$pos" RecCV (c, name :: tp, parents, ids) when (IsVariable(t)) parents[t][tp]++ when (!ids.Contains(t)) ids[t] = termId termId++ termId = 0 RecCV(a, [], oldParents, oldIds) termId = 0 RecCV(b, [], newParents, newIds) public static Filter (this s : string, f : char -> bool) : string def res = System.Text.StringBuilder () foreach (c in s) when (f (c)) _ = res.Append (c) res.ToString () [Profile]\ LCS(a : string, b : string) : int def a = a.Filter (x => ! char.IsDigit (x)) def b = b.Filter (x => ! char.IsDigit (x)) mutable tab1 = array (b.Length + 1) mutable tab2 = array (b.Length + 1) for (mutable i = 1; i <= a.Length; ++i) for (mutable j = 1; j <= b.Length; ++j) def x = if (a[i-1] == b[j-1]) 1 + tab2[j-1] else 0 tab1[j] = Math.Max(x, Math.Max(tab2[j], tab1[j-1])) (tab1, tab2) = (tab2, tab1) tab2[b.Length] #if false SimilarityStats (a : Term, old : bool) : void def ap = (if (old) oldParents else newParents)[a] log (PRUNE, $"similarity: $a") foreach (x in ap) when (x.Value != 0) log (PRUNE, $"$(x.Key) -> $(x.Value)") #endif [Profile]\ Similarity(a : Term, b : Term) : int def ap = oldParents[a] def bp = newParents[b] mutable cp = 0 foreach (x in ap) def v1 = x.Value mutable v2 _ = bp.TryGetValue (x.Key, out v2) cp += 2*Math.Min(v1, v2) - Math.Abs(v1 - v2) cp + LCS(a.Name, b.Name) // Finds a minimum weighted bipartite matching. See `selfish cities' // at http://topos.ucd.ie/rgrig/programs/index.html [Profile]\ HungarianAlgorithm(w : array[2, int]) : (array[int] * array[int]) def inf = System.Int32.MaxValue def m = w.GetLength(0) def n = w.GetLength(1) assert (m <= n) def row_mate = array(n) def col_mate = array(m) def parent_row = array(n) def unchosen_rows = array(m) def row_dec = array(m) def col_inc = array(n) def slack = array(n) mutable t = 0 // count of unchosen rows mutable q = 0 mutable delta = 0 mutable r = 0 // current row of interest mutable c = 0 // current column of interest mutable cc = 0 // second column of interest mutable unmatched_rows = m def W(r, c) w[r, c] - row_dec[r] + col_inc[c] def mate(r, c) row_mate[c] = r col_mate[r] = c for (r = 0; r < m; ++r) col_mate[r] = -1 for (c = 0; c < n; ++c) row_mate[c] = -1 while (unmatched_rows > 0) // Prepare t = 0 for (r = 0; r < m; ++r) when (col_mate[r] == -1) unchosen_rows[t] = r ++t for (c = 0; c < n; ++c) parent_row[c] = -1 slack[c] = inf // Do BFS doneBFS: for (q = 0; q < t; ++q) r = unchosen_rows[q] for (c = 0; c < n; ++c) when (W(r, c) < slack[c]) slack[c] = W(r, c) when (slack[c] == 0) parent_row[c] = r when (row_mate[c] == -1) doneBFS() unchosen_rows[t] = row_mate[c] ++t if (q == t) // No improving path found // Adjust |row_dec| and |col_inc| delta = inf for (c = 0; c < n; ++c) when (parent_row[c] == -1) delta = Math.Min(delta, slack[c]) for (c = 0; c < n; ++c) when (parent_row[c] != -1) col_inc[c] += delta for (q = 0; q < t; ++q) row_dec[unchosen_rows[q]] += delta else // Improve matching r = parent_row[c] while (col_mate[r] != -1) cc = col_mate[r] mate(r, c) c = cc r = parent_row[c] mate(r, c) --unmatched_rows // The result (col_mate, row_mate) // Populates |New| and |Old|. [Profile]\ Unify() : void // TODO: try with doubles (instead of integers) def oi = if (oldIds.Count <= newIds.Count) 0 else 1 // old index def ni = 1 - oi // new index def m = Math.Min(oldIds.Count, newIds.Count) def n = Math.Max(oldIds.Count, newIds.Count) def idxToTerm = array(2, n) def w = array (m, n) // max_similarity - similarity mutable max = 0 foreach (x in oldIds) idxToTerm[oi, x.Value] = x.Key foreach (x in newIds) idxToTerm[ni, x.Value] = x.Key for (mutable i = 0; i < m; ++i) for (mutable j = 0; j < n; ++j) def (ii, jj) = if (oi == 0) (i, j) else (j, i) w[i, j] = Similarity(idxToTerm[oi, ii], idxToTerm[ni, jj]) max = Math.Max(max, w[i, j]) for (mutable i = 0; i < m; ++i) for (mutable j = 0; j < n; ++j) w[i, j] = max - w[i, j] def ha = HungarianAlgorithm(w) def mate = if (oi == 0) ha[0] else ha[1] assert (mate.Length == oldIds.Count) for (mutable i = 0; i < mate.Length; ++i) if (mate[i] != -1) New[idxToTerm[oi, i]] = idxToTerm[ni, mate[i]] Old[idxToTerm[ni, mate[i]]] = idxToTerm[oi, i] else log(PRUNE, $"not mapping $(idxToTerm[oi, i].Name)") // Sorts terms lexicographically [Profile]\ SortTerm(t : Term) : Term use_cache(sortedTerm[t]) def comp(a, b) if (a === b) 0 else def nc = a.Name.CompareTo(b.Name) if (nc != 0) nc else Compare(a.Children, b.Children, comp) match (t.Name) // TODO: add other commutative functions | "and" | "or" | "distinct" | "=" => pool.Get(t.Name, t.Children.Map(SortTerm).Sort(comp)) | "exists" | "forall" => match (t.Children) | [qv, pats, form] => pool.Get(t.Name, [qv, pats, SortTerm(form)]) | [qv, form] => pool.Get(t.Name, [qv, SortTerm(form)]) | _ => assert (false) | _ => t.MapChildren(SortTerm) DNFSize (t : Term) : double match (t.Name) | "and" => t.Children.FoldLeft (1.0, (e, a) => a * DNFSize (e)) | "or" => t.Children.FoldLeft (0.0, (e, a) => a + DNFSize (e)) | _ => 1.0 [Profile] \ public Run (prev : Term, cur : Term) : void def grinder = Grinder(core) def prev = grinder.Skolemize(prev) def cur = grinder.Skolemize(cur) log(PRUNE, $"old_sz=$(TermSize(prev))") log(PRUNE, $"new_sz=$(TermSize(cur))") //System.IO.File.WriteAllText("q1.smt", SortTerm(prev).ToPrettyString()) //System.IO.File.WriteAllText("q2.smt", SortTerm(cur).ToPrettyString()) def newQuery = Prune(prev, cur) //MkAnd([pool.Get("not", [prev]), cur]) New.Iter((k, v) => when (k !== v) log(PRUNE, $"New[$k]=$v")) //log(TEMP, $"old: $(TermSize(prev))/$(DNFSize(prev)), " // "new: $(TermSize(cur))/$(DNFSize(cur)), " // "pruned: $(TermSize(newQuery))/$(DNFSize(newQuery))") System.IO.File.WriteAllText("pruner-prev.smt", prev.ToPrettyString()) System.IO.File.WriteAllText("pruner-cur.smt", cur.ToPrettyString()) System.IO.File.WriteAllText("pruner-pruned.smt", newQuery.ToPrettyString())