[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