[Aptitude-svn-commit] r4102 - in branches/aptitude-0.3/aptitude: . src/generic/problemresolver tests

Daniel Burrows dburrows at costa.debian.org
Sat Sep 17 03:50:15 UTC 2005


Author: dburrows
Date: Sat Sep 17 03:50:12 2005
New Revision: 4102

Added:
   branches/aptitude-0.3/aptitude/src/generic/problemresolver/conflictset.h
Modified:
   branches/aptitude-0.3/aptitude/ChangeLog
   branches/aptitude-0.3/aptitude/src/generic/problemresolver/problemresolver.h
   branches/aptitude-0.3/aptitude/tests/test_resolver.cc
Log:
Add an experimental conflict set optimized for particular situations.

Modified: branches/aptitude-0.3/aptitude/ChangeLog
==============================================================================
--- branches/aptitude-0.3/aptitude/ChangeLog	(original)
+++ branches/aptitude-0.3/aptitude/ChangeLog	Sat Sep 17 03:50:12 2005
@@ -1,5 +1,11 @@
 2005-09-16  Daniel Burrows  <dburrows at debian.org>
 
+	* tests/test_resolver.cc, src/generic/problemresolver/conflictset.h, src/generic/problemresolver/problemresolver.h:
+
+	  Add an experimental conflict set optimized under some
+	  assumptions that may or may not turn out to actually hold for
+	  real programs once I get it working.
+
 	* src/generic/resolver_manager.cc:
 
 	  Make a thread-kill override all other control messages, so that

Added: branches/aptitude-0.3/aptitude/src/generic/problemresolver/conflictset.h
==============================================================================
--- (empty file)
+++ branches/aptitude-0.3/aptitude/src/generic/problemresolver/conflictset.h	Sat Sep 17 03:50:12 2005
@@ -0,0 +1,397 @@
+// conflictset.h                                   -*-c++-*-
+//
+//   Copyright (C) 2005 Daniel Burrows
+//
+//   This program is free software; you can redistribute it and/or
+//   modify it under the terms of the GNU General Public License as
+//   published by the Free Software Foundation; either version 2 of
+//   the License, or (at your option) any later version.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//   General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; see the file COPYING.  If not, write to
+//   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+//   Boston, MA 02111-1307, USA.
+//
+// A data structure to store the set of conflicts in the problem
+// resolver, highly optimized for the specific case of the problem
+// resolver.
+//
+// The key point about this structure is that it's somewhat expensive
+// to iterate over all the members of a solution.  On the other hand,
+// testing for membership in a solution seems to be pretty efficient.
+// Furthermore, even fairly long searches produce few conflicts, and
+// the conflicts they produce are small.  Thus, it is perhaps somewhat
+// more efficient to create a large, easy-to-scan array of the
+// elements in the conflicts, then testing each element of the array
+// against the incoming solutions.
+//
+// Furthermore, by arranging the array as a binary tree, we can use
+// short-circuiting to quickly discard conflicts (in the case that
+// there are a lot of them).  The idea is that (a) most solutions
+// won't intersect any given conflict, and (b) if several conflicts
+// share an element, it's more efficient to test against that element
+// before the other elements in those conflicts, since we could
+// discard all of them at once.
+//
+// Plan: the tree nodes correspond to elements.  The left child of
+// each node represents all the sets that contain an element matching
+// the node's element, while the right child represents the rest of
+// the sets.  A search on a given node proceeds like this:
+//
+//  - searches on the empty node fail;
+//  - searches on a leaf return the corresponding set;
+//  - searches on a node whose element is NOT contained in the
+//    incoming solution recur into its right child;
+//  - searches on a node whose element IS contained in the incoming
+//    solution recur into both children (first left then right).
+//
+// In building the tree we greedily try to make the left child of each
+// node as large as possible; the idea being to abort the search as
+// soon as possible, or at least to discard as many nodes as possible
+// at each step.  At each step, the input is a list of nodes that are
+// supersets of the current "environment" (defined as the set of
+// elements that are known to be included, by virtue of having
+// descended into their left chidlren).  Note that if any set exactly
+// matches the current "environment", then the rest of the nodes in
+// the list are *redundant* and can be deleted.
+//
+// It's not clear how to build this tree iteratively, but new
+// conflicts arise at a low enough rate that it should be feasible to
+// rebuild it from scratch at every opportunity.
+
+#ifndef CONFLICTSET_H
+#define CONFLICTSET_H
+
+#include "solution.h"
+
+#include <iostream>
+#include <list>
+#include <set>
+
+/** This is similar to setset, but optimized for use in the problem
+ *  resolver.
+ */
+template<typename PackageUniverse>
+class conflictset
+{
+private:
+  typedef typename PackageUniverse::package package;
+  typedef typename PackageUniverse::version version;
+  typedef generic_solution<PackageUniverse> solution;
+  typedef typename solution::action action;
+
+   /** \param conflictorpair a (package, action) pair contained in a conflict.
+    *  \param apair a (package, action) pair from a solution or a conflict.
+    *
+    *  \return \b true if the given conflictor matches a.  This relation
+    *          is transitive, so if c1 matches c2 and c2 matches a,
+    *          then c1 matches a.
+    */
+  static bool conflictor_matches(const action &conflictor,
+				 const action &a)
+  {
+    if(a.ver != conflictor.ver)
+      return false;
+
+    if(!conflictor.from_dep_source)
+      return true;
+    else
+      return a.from_dep_source && a.d == conflictor.d;
+  }
+
+  /** \return \b true if each element of pc2 is matched by an element
+   *  in pc1.
+   */
+  static bool conflict_matches(const typename imm::map<package, action> &c,
+			       const typename imm::map<package, action> &acts)
+  {
+    typename imm::map<package, action>::const_iterator ci = c.begin();
+    typename imm::map<package, action>::const_iterator ai = acts.begin();
+
+    while(ci != c.end() &&
+	  ai != acts.end())
+      {
+	if(ai->first < ci->first)
+	  ++ai;
+	else if(ci->first < ai->first)
+	  return false;
+	else if(!(conflictor_matches(ci->second, ai->second)))
+	  return false;
+	else
+	  {
+	    ++ci;
+	    ++ai;
+	  }
+      }
+
+    return (ci == c.end());
+  }
+
+  // The current set of sets.  Expressed as a list for the sake of
+  // iterator stability.
+  typedef std::list<imm::map<package, action> > entries_list;
+
+  /** A node in the tree.  A node for which val != entries.end()
+   *  is a leaf.
+   */
+  struct node
+  {
+    /** The number of elements to move forward in the array to reach
+     *	the right child of this node.  If 0, there is no right child.
+     */
+    unsigned int right_offset;
+
+    /** Either entries.end() or an iterator representing the contents
+     *  of this node.
+     */
+    typename entries_list::const_iterator val;
+
+    /** The split element. */
+    action act;
+
+    node(unsigned int _right_offset,
+	 typename entries_list::const_iterator _val,
+	 const action &_act)
+      :right_offset(_right_offset),
+       val(_val),
+       act(_act)
+    {
+    }
+  };
+
+  entries_list entries;
+
+  /** The search tree as an array. */
+  std::vector<node> tree;
+
+  void reset_counts() const
+  {
+    for(typename entries_list::const_iterator i = entries.begin();
+	i != entries.end(); ++i)
+      i->hit_count = 0;
+  }
+
+  /** Build a tree from the given set of conflicts.  Assumes that
+   *  entries itself is initially empty (or at least doesn't contain
+   *  the incoming set); each time that a node with an attached value
+   *  is generated, place the value in entries.  All new elements will
+   *  be placed in the 'tree' structure.
+   *
+   *  \param conflicts a list of the conflicts to place in this branch
+   *  of the tree.
+   *
+   *  \param candidate_splits a set of possible splitters
+   *
+   *  \param env the current environment.
+   */
+  void build_tree(const typename std::vector<imm::map<package, action> > &conflicts,
+		  const imm::map<package, action> &env)
+  {
+    assert(!conflicts.empty());
+
+    std::set<std::pair<package, action> > candidate_splits;
+    std::vector<imm::map<package, action> > left_children, right_children;
+
+    for(typename std::vector<imm::map<package, action> >::const_iterator
+	  ci = conflicts.begin(); ci != conflicts.end(); ++ci)
+      {
+	if(conflict_matches(*ci, env))
+	  {
+	    entries.push_back(*ci);
+	    typename entries_list::const_iterator tmpi = entries.end();
+	    --tmpi;
+	    tree.push_back(node(0, tmpi, action()));
+	    return;
+	  }
+
+	for(typename imm::map<package, action>::const_iterator
+	      ai = ci->begin(); ai != ci->end(); ++ai)
+	  {
+	    typename imm::map<package, action>::node
+	      found = env.lookup(ai->first);
+
+	    if(!(found.isValid() &&
+		 conflictor_matches(ai->second, found.getVal().second)))
+	      candidate_splits.insert(*ai);
+	  }
+      }
+
+    typename std::set<std::pair<package, action> >::const_iterator
+      best_split = candidate_splits.end();
+
+    // Ok, no full subsumption exists.  Try to find a good split.
+    for(typename std::set<std::pair<package, action> >::const_iterator i
+	  = candidate_splits.begin(); i != candidate_splits.end(); ++i)
+      {
+	std::vector<imm::map<package, action> >
+	  candidate_left_children, candidate_right_children;
+
+	for(typename std::vector<imm::map<package, action> >::const_iterator
+	      ci = conflicts.begin(); ci != conflicts.end(); ++ci)
+	  {
+	    typename imm::map<package, action>::node found
+	      = ci->lookup(i->first);
+
+	    if(found.isValid() &&
+	       conflictor_matches(found.getVal().second, i->second))
+	      candidate_left_children.push_back(*ci);
+	    else
+	      candidate_right_children.push_back(*ci);
+	  }
+
+	// Update the "best" choice if either the current "best"
+	// choice is empty or the new candidate has a strictly larger
+	// left branch.
+	if((left_children.empty() && right_children.empty()) ||
+	   left_children.size() < candidate_left_children.size())
+	  {
+	    best_split = i;
+	    left_children = candidate_left_children;
+	    right_children = candidate_right_children;
+	  }
+      }
+
+    assert(best_split != candidate_splits.end());
+    assert(!left_children.empty());
+
+    typename std::vector<node>::size_type curr_elt = tree.size();
+    // NB: we don't know what the right child's offset is yet.
+    tree.push_back(node(0, entries.end(), best_split->second));
+    build_tree(left_children,
+	       imm::map<package, action>::bind(env,
+					       best_split->first,
+					       best_split->second));
+
+    if(!right_children.empty())
+      {
+	typename std::vector<node>::size_type right_elt = tree.size();
+	tree[curr_elt].right_offset = right_elt - curr_elt;
+	build_tree(right_children, env);
+      }
+  }
+
+  static void dump(std::ostream &out, const action &act)
+  {
+    out << act.ver.get_package().get_name() << " "
+	<< act.ver.get_name();
+
+    if(act.from_dep_source)
+      out << " [" << act.d << "]";
+  }
+
+  void dump(std::ostream &out,
+	    typename std::vector<node>::const_iterator elt,
+	    const std::string &prefix = "",
+	    unsigned int indent = 0) const
+  {
+    if(elt != tree.end())
+      {
+	if(indent >= prefix.size())
+	  for(unsigned int i = 0; i < indent - prefix.size(); ++i)
+	    out << " ";
+	out << prefix;
+
+	if(elt->val != entries.end())
+	  {
+	    out << "{";
+	    for(typename imm::map<package, action>::const_iterator ai
+		  = elt->val->begin(); ai != elt->val->end(); ++ai)
+	      {
+		if(ai != elt->val->begin())
+		  out << ", ";
+
+		out << ai->second.ver.get_package().get_name()
+		    << " " << ai->second.ver.get_name();
+
+		if(ai->second.from_dep_source)
+		  out << "[" << ai->second.d << "]";
+	      }
+	    out << "}" << std::endl;
+	  }
+	else
+	  {
+	    out << "(";
+	    dump(out, elt->act);
+	    out << ")" << std::endl;
+
+	    dump(out, elt + 1, "L: ", indent+4);
+	    if(elt->right_offset > 0)
+	      dump(out, elt + elt->right_offset ,"R: ", indent+4);
+	  }
+      }
+  }
+
+public:
+  typedef typename entries_list::const_iterator const_iterator;
+  typedef typename entries_list::size_type size_type;
+
+  conflictset()
+  {
+  }
+
+  const_iterator begin() const
+  {
+    return entries.begin();
+  }
+
+  const_iterator end() const
+  {
+    return entries.end();
+  }
+
+  size_type size() const
+  {
+    return entries.size();
+  }
+
+  /** Add a new set into this set. */
+  void insert(const imm::map<package, action> &s)
+  {
+    std::vector<imm::map<package, action> > new_entries(entries.begin(), entries.end());
+    new_entries.push_back(s);
+
+    tree.clear();
+    entries.clear();
+    build_tree(new_entries, imm::map<package, action>());
+  }
+
+  const_iterator find_matching_conflict(const imm::map<package, action> &s) const
+  {
+    typename std::vector<node>::const_iterator i = tree.begin();
+
+    while(i != tree.end())
+      {
+	if(i->val != entries.end())
+	  return i->val;
+
+	bool matches;
+
+	typename imm::map<package, action>::node found = s.lookup(i->act.ver.get_package());
+	if(found.isValid())
+	  matches = conflictor_matches(i->act, found.getVal().second);
+	else
+	  matches = false;
+
+	if(matches)
+	  ++i;
+	else if(i->right_offset > 0)
+	  i += i->right_offset;
+	else
+	  ++i;
+      }
+
+    return entries.end();
+  }
+
+  void dump(std::ostream &out) const
+  {
+    dump(out, tree.begin());
+  }
+};
+
+#endif

Modified: branches/aptitude-0.3/aptitude/src/generic/problemresolver/problemresolver.h
==============================================================================
--- branches/aptitude-0.3/aptitude/src/generic/problemresolver/problemresolver.h	(original)
+++ branches/aptitude-0.3/aptitude/src/generic/problemresolver/problemresolver.h	Sat Sep 17 03:50:12 2005
@@ -49,11 +49,11 @@
 
 #include <iostream>
 
+#include "conflictset.h"
 #include "dump_universe.h"
 #include "exceptions.h"
 #include "solution.h"
 
-#include "../dense_setset.h"
 #include "../threads.h"
 
 /** A dummy iterator that's always an "end" iterator. */
@@ -364,12 +364,10 @@
     */
    std::set<solution, solution_contents_compare> deferred;
 
-   typedef dense_mapset<package, action, ExtractPackageId> conflictset;
-
    /** Stores conflicts: sets of installations that have been
     *  determined to be mutually incompatible.
     */
-   conflictset conflicts;
+   conflictset<PackageUniverse> conflicts;
 
    /** The initial set of broken dependencies.  Kept here for use in
     *  the stupid-elimination algorithm.
@@ -401,73 +399,22 @@
    std::ostream &dump_conflict(std::ostream &out, const imm::map<package, action> &conflict) const;
    std::ostream &dump_conflict(std::ostream &out, const action &act) const;
 
-
-   /** \param conflictorpair a (package, action) pair contained in a conflict.
-    *  \param apair a (package, action) pair from a solution or a conflict.
-    *
-    *  \return \b true if the given conflictor matches a.  This relation
-    *          is transitive, so if c1 matches c2 and c2 matches a,
-    *          then c1 matches a.
-    */
-   static bool conflictor_matches(const std::pair<package, action> &conflictorpair,
-				  const std::pair<package, action> &apair)
-     {
-       const action &conflictor = conflictorpair.second;
-       const action &a = apair.second;
-
-       if(a.ver != conflictor.ver)
-	 return false;
-
-       if(!conflictor.from_dep_source)
-	 return true;
-       else
-	 return a.from_dep_source && a.d == conflictor.d;
-     }
-
-   /** \return \b true if each element of pc2 is matched by an element
-    *  in pc1.
-    */
-   static bool conflict_matches(const typename imm::map<package, action> &c,
-				const typename imm::map<package, action> &acts)
-   {
-     typename imm::map<package, action>::const_iterator ci = c.begin();
-     typename imm::map<package, action>::const_iterator ai = acts.begin();
-
-     while(ci != c.end() &&
-	   ai != acts.end())
-       {
-	 if(ai->first < ci->first)
-	   ++ai;
-	 else if(ci->first < ai->first)
-	   return false;
-	 else if(!(conflictor_matches(ci->second, ai->second)))
-	   return false;
-	 else
-	   {
-	     ++ci;
-	     ++ai;
-	   }
-       }
-
-     return (ci == c.end());
-   }
-
    /** Test whether the given partial conflict subsumes an existing
     *  conflict.
     *
     *  \return a conflict matched by m, or conflicts.end() if no such
     *  conflict exists.
     */
-   typename conflictset::const_iterator
+   typename conflictset<PackageUniverse>::const_iterator
    find_matching_conflict(const imm::map<package, action> &m) const
    {
-     return conflicts.find_submap(m, &conflictor_matches);
+     return conflicts.find_matching_conflict(m);
    }
 
    /** Test whether the given solution contains a conflict. */
    bool contains_conflict(const solution &s) const
    {
-     typename conflictset::const_iterator
+     typename conflictset<PackageUniverse>::const_iterator
        found = find_matching_conflict(s.get_actions());
      bool rval = (found != conflicts.end());
 
@@ -490,7 +437,7 @@
     */
    void add_conflict(const imm::map<package, action> &conflict)
    {
-     typename conflictset::const_iterator
+     typename conflictset<PackageUniverse>::const_iterator
        found = find_matching_conflict(conflict);
 
      if(found != conflicts.end())
@@ -1656,7 +1603,7 @@
 	 imm::map<package, action> new_acts = s.get_actions();
 	 new_acts.put(v.get_package(), act);
 
-	 typename conflictset::const_iterator
+	 typename conflictset<PackageUniverse>::const_iterator
 	   found = find_matching_conflict(new_acts);
 
 	 if(found == conflicts.end())
@@ -1916,8 +1863,7 @@
       minimum_score(-infinity), max_successors(_max_successors),
       universe(_universe), finished(false), deferred_dirty(false),
       debug(false), remove_stupid(true),
-      solver_executing(false), solver_cancelled(false),
-      conflicts(_universe.get_package_count())
+      solver_executing(false), solver_cancelled(false)
   {
     // Find all the broken deps.
     for(typename PackageUniverse::broken_dep_iterator bi=universe.broken_begin();

Modified: branches/aptitude-0.3/aptitude/tests/test_resolver.cc
==============================================================================
--- branches/aptitude-0.3/aptitude/tests/test_resolver.cc	(original)
+++ branches/aptitude-0.3/aptitude/tests/test_resolver.cc	Sat Sep 17 03:50:12 2005
@@ -19,6 +19,7 @@
 //
 // A test of the generic resolver layer.
 
+#include <src/generic/problemresolver/conflictset.h>
 #include <src/generic/problemresolver/dummy_universe.h>
 #include <src/generic/problemresolver/problemresolver.h>
 
@@ -83,10 +84,17 @@
   CPPUNIT_TEST(testActionCompare);
   CPPUNIT_TEST(testSolutionCompare);
   CPPUNIT_TEST(testRejections);
+  CPPUNIT_TEST(testConflictSet);
 
   CPPUNIT_TEST_SUITE_END();
 
 private:
+  typedef dummy_universe_ref::dep dep;
+  typedef dummy_universe_ref::package package;
+  typedef dummy_universe_ref::version version;
+  typedef generic_solution<dummy_universe_ref> solution;
+  typedef solution::action action;
+
   static dummy_universe_ref parseUniverse(const std::string &s)
   {
     std::istringstream in(s);
@@ -298,6 +306,72 @@
 	CPPUNIT_FAIL("Expected at least one solution, got none.");
       }
   }
+
+  void testConflictSet()
+  {
+    dummy_universe_ref u = parseUniverse(dummy_universe_1);
+
+    conflictset<dummy_universe_ref> conflicts;
+
+    imm::map<package, action> cf1;
+    imm::map<package, action> cf2;
+    imm::map<package, action> cf3;
+
+    package a = u.find_package("a");
+    package b = u.find_package("b");
+    package c = u.find_package("c");
+
+    version av1 = a.version_from_name("v1");
+    version av2 = a.version_from_name("v2");
+    version av3 = a.version_from_name("v3");
+
+    version bv1 = b.version_from_name("v1");
+    version bv2 = b.version_from_name("v2");
+    version bv3 = b.version_from_name("v3");
+
+    version cv1 = c.version_from_name("v1");
+    version cv2 = c.version_from_name("v2");
+    version cv3 = c.version_from_name("v3");
+
+    cf1.put(a,  action(av1, dep(), false, 0));
+
+    cf2.put(a, action(av2, dep(), false, 0));
+    cf2.put(b, action(bv3, dep(), false, 0));
+
+    cf3.put(a, action(av2, dep(), false, 0));
+    cf3.put(c, action(cv1, dep(), false, 0));
+
+    conflicts.insert(cf1);
+    conflicts.insert(cf2);
+    conflicts.insert(cf3);
+
+    conflicts.dump(std::cerr);
+    CPPUNIT_ASSERT_EQUAL((size_t) 3, conflicts.size());
+
+    imm::map<package, action> s;
+    s.put(a, action(av3, dep(), false, 0));
+    s.put(b, action(bv2, dep(), false, 0));
+    s.put(c, action(cv1, dep(), false, 0));
+
+    CPPUNIT_ASSERT(conflicts.find_matching_conflict(s) == conflicts.end());
+
+    s.put(a, action(av2, dep(), false, 0));
+    CPPUNIT_ASSERT(conflicts.find_matching_conflict(s) != conflicts.end());
+    CPPUNIT_ASSERT(*conflicts.find_matching_conflict(s) == cf3);
+
+    s.put(a, action(av1, dep(), false, 0));
+    CPPUNIT_ASSERT(conflicts.find_matching_conflict(s) != conflicts.end());
+    CPPUNIT_ASSERT(*conflicts.find_matching_conflict(s) == cf1);
+
+    s.put(c, action(cv3, dep(), false, 0));
+    s.put(a, action(av2, dep(), false, 0));
+
+    CPPUNIT_ASSERT(conflicts.find_matching_conflict(s) == conflicts.end());
+
+    s.put(b, action(bv3, dep(), false, 0));
+    CPPUNIT_ASSERT(conflicts.find_matching_conflict(s) != conflicts.end());
+    CPPUNIT_ASSERT(*conflicts.find_matching_conflict(s) == cf2);
+  }
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(ResolverTest);



More information about the Aptitude-svn-commit mailing list