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

Daniel Burrows dburrows at costa.debian.org
Wed Aug 24 23:38:52 UTC 2005


Author: dburrows
Date: Wed Aug 24 23:38:47 2005
New Revision: 3943

Modified:
   branches/aptitude-0.3/aptitude/ChangeLog
   branches/aptitude-0.3/aptitude/src/generic/problemresolver/problemresolver.h
   branches/aptitude-0.3/aptitude/src/generic/problemresolver/solution.h
   branches/aptitude-0.3/aptitude/tests/test_resolver.cc
Log:
Refactor the problem resolver to be more comprehensible and reasonably sized.

Modified: branches/aptitude-0.3/aptitude/ChangeLog
==============================================================================
--- branches/aptitude-0.3/aptitude/ChangeLog	(original)
+++ branches/aptitude-0.3/aptitude/ChangeLog	Wed Aug 24 23:38:47 2005
@@ -1,3 +1,23 @@
+2005-08-24  Daniel Burrows  <dburrows at debian.org>
+
+	* src/generic/problemresolver/problemresolver.h, src/generic/problemresolver/solution.h, tests/test_resolver.h:
+
+	  Massive refactoring of the problem resolver.  The solution
+	  abstraction is now much stronger: solutions can *only* be
+	  constructed as either a root node or a successor to an existing
+	  solution, and score calculation is handled in the constructor
+	  (actually a static factory function).
+
+	  Instead of trying hard to not actually build solution objects,
+	  the main resolver now generates all solutions up-front; this
+	  introduces some new inefficiencies while removing others, but it
+	  does seem to have slightly slowed the program down.
+
+	  Needless to say, separating solution generation from solution
+	  processing makes everything so much cleaner that it's not even
+	  funny.  Some errors may also been introduced that will need to
+	  be dealt with; actual testing is probably a good idea here.
+
 2005-08-23  Daniel Burrows  <dburrows at debian.org>
 
 	* src/generic/problemresolver/problemresolver.h:

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	Wed Aug 24 23:38:47 2005
@@ -232,10 +232,10 @@
     }
   };
 
-  /** How much to reward long and/or broken solutions.  Typically
-   *  negative to penalize such things, or 0 to ignore them.
-   */
-  int step_score, broken_score, unfixed_soft_score;
+  // Information regarding the weight given to various parameters;
+  // packaged up in a struct so it can be easily used by the solution
+  // constructors.
+  solution_weights weights;
 
   /** Solutions whose score is smaller than this value will be
    *  discarded rather than being enqueued.
@@ -264,24 +264,16 @@
    *  of the queue and likely stay there), while if a large number of
    *  unrelated packages are broken, it doesn't matter which successor
    *  goes first.
+   *
+   *  Note that in practice, turning this up is very likely to result
+   *  in dreadful performance; the option to do so may very well be
+   *  removed in the future.
    */
   unsigned int max_successors;
 
-  /** How much to reward real solutions -- solutions that fix all
-   *  dependencies.  Make this big to immediately pick them up, or
-   *  small to ignore "bad" solutions (at the risk of running out of
-   *  time if no better solution pops up).
-   */
-  int full_solution_score;
-
   /** The universe in which we are solving problems. */
   const PackageUniverse universe;
 
-  /** The internal scores to apply to individual package
-   *  versions.
-   */
-  int *version_scores;
-
   /** If \b true, we have exhausted the list of solutions. */
   bool finished:1;
 
@@ -309,102 +301,10 @@
    */
   std::set<solution, solution_contents_compare> deferred;
 
-  /** An action implicated in a conflict: this can either be a pure
-   *  version installation (i.e., the version is implicated no matter
-   *  how it's installed) or a dep-source-qualified version
-   *  installation (i.e., the version is only implicated if it's
-   *  installed to fix the source of a particular dep).
-   */
-  struct act_conflict
-  {
-    /** The version to install. */
-    version ver;
-
-    /** The dependency whose source was fixed by this, if
-     *  from_dep_source is \b true.
-     */
-    dep d;
-
-    bool from_dep_source;
-
-    act_conflict()
-      :from_dep_source(false)
-    {
-    }
-
-    act_conflict(const version &_ver)
-      :ver(_ver), from_dep_source(false)
-    {
-    }
-
-    act_conflict(const version &_ver, const dep &_d)
-      :ver(_ver), d(_d), from_dep_source(true)
-    {
-    }
-
-    /** \return \b true if this action will match the action given by
-     *          other: true if the versions are equal and either they
-     *          both follow from the same dep, or 'other' doesn't
-     *          follow from any dep.
-     */
-    bool subsumes(const act_conflict &other) const
-    {
-      if(ver != other.ver)
-	return false;
-
-      if(!other.from_dep_source)
-	return true;
-      else
-	return from_dep_source && d == other.d;
-    }
-
-    bool operator==(const act_conflict &other) const
-    {
-      if(from_dep_source != other.from_dep_source)
-	return false;
-
-      if(from_dep_source)
-	return ver == other.ver && d == other.d;
-      else
-	return ver == other.ver;
-    }
-
-    bool operator!=(const act_conflict &other) const
-    {
-      if(from_dep_source != other.from_dep_source)
-	return true;
-
-      if(from_dep_source)
-	return ver != other.ver || d != other.d;
-      else
-	return ver != other.ver;
-    }
-
-    bool operator<(const act_conflict &other) const
-    {
-      if(!from_dep_source && other.from_dep_source)
-	return true;
-      else if(from_dep_source && !other.from_dep_source)
-	return false;
-      else if(!from_dep_source)
-	return ver < other.ver;
-      else if(ver < other.ver) // if(from_dep_source)
-	return true;
-      else if(other.ver < ver)
-	return false;
-      else
-	return d < other.d;
-    }
-  };
-
-  // Needs to be a friend since it has to be global.
-  template<class PackageUniverse2>
-  friend std::ostream &operator<<(std::ostream &out, const typename std::map<typename PackageUniverse2::package, typename generic_problem_resolver<PackageUniverse2>::act_conflict> &ac);
-
   /** Stores conflicts: sets of installations that have been
    *  determined to be mutually incompatible.
    */
-  std::set<std::map<package, act_conflict> > conflicts;
+  std::set<std::map<package, action> > conflicts;
 
   /** The initial set of broken dependencies.  Kept here for use in
    *  the stupid-elimination algorithm.
@@ -432,59 +332,69 @@
 
   typedef std::set<std::pair<version, version> > stupid_table;
 
-  // FIXME: temporary hack so I don't have to lift code all over.
-  std::ostream &dump(std::ostream &out, const std::map<package, act_conflict> &act) const;
-  std::ostream &dump(std::ostream &out, const act_conflict &act) const;
+  std::ostream &dump_conflict(std::ostream &out, const std::map<package, action> &conflict) const;
+  std::ostream &dump_conflict(std::ostream &out, const action &act) const;
 
-  /** Populate a 'partial conflict map' from a solution. */
-  static void populate_partial_conflict(const solution &s,
-					typename std::map<package, act_conflict> &pc)
-  {
-    for(typename std::map<package, action>::const_iterator si
-	  = s.get_actions().begin(); si != s.get_actions().end(); ++si)
-      pc[si->first] = act_conflict(si->second.ver);
-  }
 
-  /** \return \b true if each element of pc2 is subsumed by an element
+  /** \param conflictor an action contained in a conflict.
+   *  \param a an action 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 partial_conflict_subsumes(const typename std::map<package, act_conflict> &pc1,
-					const typename std::map<package, act_conflict> &pc2)
+  static bool conflict_matches(const typename std::map<package, action> &c,
+			       const typename std::map<package, action> &acts)
   {
-    typename std::map<package, act_conflict>::const_iterator pc1i = pc1.begin();
-    typename std::map<package, act_conflict>::const_iterator pc2i = pc2.begin();
+    typename std::map<package, action>::const_iterator ci = c.begin();
+    typename std::map<package, action>::const_iterator ai = acts.begin();
 
-    while(pc1i != pc1.end() &&
-	  pc2i != pc2.end())
+    while(ci != c.end() &&
+	  ai != acts.end())
       {
-	if(pc1i->first < pc2i->first)
-	  ++pc1i;
-	else if(pc2i->first < pc1i->first)
+	if(ai->first < ci->first)
+	  ++ai;
+	else if(ci->first < ai->first)
 	  return false;
-	else if(!(pc1i->second.subsumes(pc2i->second)))
+	else if(!(conflictor_matches(ci->second, ai->second)))
 	  return false;
 	else
 	  {
-	    ++pc1i;
-	    ++pc2i;
+	    ++ci;
+	    ++ai;
 	  }
       }
 
-    return (pc2i == pc2.end());
+    return (ci == c.end());
   }
 
   /** Test whether the given partial conflict subsumes an existing
    *  conflict.
    *
-   *  \return a conflict subsumed by m, or conflicts.end() if no such
+   *  \return a conflict matched by m, or conflicts.end() if no such
    *  conflict exists.
    */
-  typename std::set<std::map<package, act_conflict> >::const_iterator
-  subsumes_any_conflict(const std::map<package, act_conflict> &m) const
+  typename std::set<std::map<package, action> >::const_iterator
+  matches_any_conflict(const std::map<package, action> &m) const
   {
-    for(typename std::set<std::map<package, act_conflict> >::const_iterator ci = conflicts.begin();
+    for(typename std::set<std::map<package, action> >::const_iterator ci = conflicts.begin();
 	ci != conflicts.end(); ++ci)
-	if(partial_conflict_subsumes(m, *ci))
+	if(conflict_matches(*ci, m))
 	  return ci;
 
     return conflicts.end();
@@ -493,17 +403,14 @@
   /** Test whether the given solution contains a conflict. */
   bool contains_conflict(const solution &s) const
   {
-    std::map<package, act_conflict> m;
-    populate_partial_conflict(s, m);
-
-    typename std::set<std::map<package, act_conflict> >::const_iterator
-      found = subsumes_any_conflict(m);
+    typename std::set<std::map<package, action> >::const_iterator
+      found = matches_any_conflict(s.get_actions());
     bool rval = (found != conflicts.end());
 
     if(debug && rval)
       {
 	std::cout << "The conflict ";
-	dump(std::cout, *found);
+	dump_conflict(std::cout, *found);
 	std::cout << " is triggered by the solution ";
 	s.dump(std::cout);
 	std::cout << std::endl;
@@ -512,6 +419,55 @@
     return rval;
   }
 
+  /** Add the given conflict to the set of conflicts.  Tests if the
+   *  conflict is matched by an existing conflict (in which case it's
+   *  redundant and will be dropped), then tests whether any existing
+   *  conflicts will be made redundant by this conflict.
+   */
+  void add_conflict(const std::map<package, action> &conflict)
+  {
+    typename std::set<std::map<package, action> >::const_iterator
+      found = matches_any_conflict(conflict);
+
+    if(found != conflicts.end())
+      {
+	if(debug)
+	  {
+	    std::cout << "Dropping conflict ";
+	    dump_conflict(std::cout, conflict);
+	    std::cout << " because it is redundant with ";
+	    dump_conflict(std::cout, *found);
+	    std::cout << std::endl;
+	  }
+      }
+    else
+      {
+	for(typename std::set<std::map<package, action> >::const_iterator ci = conflicts.begin(), cj;
+	    ci != conflicts.end(); ci = cj)
+	  {
+	    cj = ci;
+	    ++cj;
+
+	    if(conflict_matches(conflict, *ci))
+	      {
+		if(debug)
+		  {
+		    std::cout << "Dropping conflict ";
+		    dump_conflict(std::cout, *ci);
+		    std::cout << " because it is redundant with ";
+		    dump_conflict(std::cout, conflict);
+		    std::cout << std::endl;
+		  }
+
+		conflicts.erase(ci);
+	      }
+	  }
+
+	conflicts.insert(conflict);
+      }
+  }
+
+#if 0
   /** Test whether the given solution contains a conflict when the
    *  given action is taken.
    *
@@ -866,7 +822,6 @@
     return project_iter_2nd_impl<Iter>(i);
   }
 
-
   /** Wrap an action iterator to select its version element. */
   template<class Iter>
   class project_ver_impl
@@ -953,87 +908,6 @@
     return apt_iter_wrapper_impl<Iter>(begin, end);
   }
 
-  /** Calculate a new set of broken dependencies given an a starting
-   *  solution object, old set of broken dependencies and a range of
-   *  versions to install.
-   *
-   *  \param s the starting package -> version map
-   *  \param vbegin the start of the range of versions to install
-   *  \param vend the end of the range of versions to install
-   *  \param unresolved_soft_deps a set of soft dependencies to ignore
-   *  \param old_broken the old set of broken dependencies
-   *  \param new_broken a set into which the newly broken dependencies
-   *                    should be inserted.
-   */
-  template<typename VerIter, typename SolutionType>
-  void update_broken(const SolutionType &s,
-		     const VerIter &vbegin,
-		     const VerIter &vend,
-		     const std::set<dep> &unresolved_soft_deps,
-		     const std::set<dep> &old_broken,
-		     std::set<dep> &new_broken)
-  {
-    // Check all revdeps of the old AND new package versions, and all
-    // previously broken deps.  Is there any way to speed this up?
-    // For instance, by keeping a cache of already-examined deps.
-    // Note that for the old version we only care about Depends and
-    // for the new we only care about Conflicts...but it's not clear
-    // that information can really be used in any good way (aside from
-    // satisfied_by()).
-    //
-    // Note that this might be a rather expensive step -- on the other
-    // hand, if lots of deps are broken, we might have problems
-    // anyway.  Just plowing ahead for now.
-    //
-    // If you touch this code, remember that the revdeps list is not a
-    // full list of the reverse dependencies; the constraint on it is
-    // that any deps newly broken by installing v must appear in
-    // *either* the reverse list of v *or* the reverse list of
-    // old_version.
-
-
-
-    // Generate a temporary solution mapping.  Expensive and probably
-    // redundant with other code, should it be passed in as an
-    // argument?
-    partial_solution<SolutionType> tmpsol(s);
-    tmpsol.install(vbegin, vend);
-
-
-    for(VerIter vi=vbegin; vi!=vend; ++vi)
-      {
-	const version &v=*vi;
-	version old_version=s.version_of(v.get_package());
-
-	// Check reverse deps of the old version
-	for(typename version::revdep_iterator rd=old_version.revdeps_begin();
-	    !rd.end(); ++rd)
-	  if(unresolved_soft_deps.find(*rd) == unresolved_soft_deps.end() &&
-	     (*rd).broken_under(tmpsol))
-	    new_broken.insert(*rd);
-
-	// Check reverse deps of the new version
-	for(typename version::revdep_iterator rd=v.revdeps_begin();
-	    !rd.end(); ++rd)
-	  if(unresolved_soft_deps.find(*rd) == unresolved_soft_deps.end() &&
-	     (*rd).broken_under(tmpsol))
-	    new_broken.insert(*rd);
-
-	// Check forward deps of the new version
-	for(typename version::dep_iterator di=v.deps_begin();
-	    !di.end(); ++di)
-	  if(unresolved_soft_deps.find(*di) == unresolved_soft_deps.end() &&
-	     (*di).broken_under(tmpsol))
-	    new_broken.insert(*di);
-      }
-
-    for(typename std::set<dep>::const_iterator bd=old_broken.begin();
-	bd!=old_broken.end(); ++bd)
-      if(unresolved_soft_deps.find(*bd) == unresolved_soft_deps.end() &&
-	 (*bd).broken_under(tmpsol))
-	new_broken.insert(*bd);
-  }
-
 
   /** Given a list of actions, remove "obviously" unnecessary actions;
    *  that is, actions which are not inspired by any broken
@@ -1294,6 +1168,15 @@
 
     return rval;
   }
+#endif
+
+  solution eliminate_stupid(const solution &s) const
+  {
+    if(debug)
+      std::cout << "Would eliminate stupid, but stupid elimination is disabled." << std::endl;
+
+    return s;
+  }
 
   /** Calculate whether the solution is rejected based on
    *  user_rejected by testing whether the intersection of the
@@ -1479,60 +1362,6 @@
     return false;
   }
 
-  /** Generates a successor to s by performing the actions
-   *  [abegin,aend) and ignoring the (soft) deps [ubegin,uend).
-   */
-  template<class forbid_iter, class a_iter, class u_iter>
-  solution do_install(const solution &s,
-		      const a_iter &abegin, const a_iter &aend,
-		      const u_iter &ubegin, const u_iter &uend,
-		      const forbid_iter &forbidden_iter)
-  {
-    int new_action_score=s.get_action_score();
-
-    for(a_iter a=abegin; a!=aend; ++a)
-      {
-	const version &v=a->ver;
-	version old_version=s.version_of(v.get_package());
-
-	assert(old_version == v.get_package().current_version() &&
-	       old_version != v);
-
-	new_action_score+=step_score;
-
-	assert(v.get_id()<universe.get_version_count());
-	new_action_score+=version_scores[v.get_id()];
-	assert(v.get_package().current_version().get_id()<universe.get_version_count());
-	new_action_score-=version_scores[v.get_package().current_version().get_id()];
-      }
-
-
-    std::set<dep> new_broken;
-    std::set<dep> new_unresolved_soft(s.get_unresolved_soft_deps());
-    new_unresolved_soft.insert(ubegin, uend);
-    update_broken(s, project_ver(abegin),
-		  project_ver(aend),
-		  new_unresolved_soft,
-		  s.get_broken(),
-		  new_broken);
-
-    for(u_iter u = ubegin; u != uend; ++u)
-      new_broken.erase(*u);
-
-    int new_score=new_action_score + broken_score * new_broken.size()
-      + unfixed_soft_score * new_unresolved_soft.size();
-
-    if(new_broken.size() == 0)
-      new_score += full_solution_score;
-
-    return solution(abegin, aend, s,
-		    ubegin, uend,
-		    new_broken,
-		    forbidden_iter,
-		    new_score,
-		    new_action_score);
-  }
-
   /** Tries to enqueue the given package.
    *
    *  \return \b true if the solution was not irrelevant.
@@ -1577,441 +1406,350 @@
       }
   }
 
-  /** Enqueues a single successor node to the given solution by
-   *  installing the given package versions.
-   *
-   *  \param s the predecessor of the new solution
-   *  \param v the version to install
-   *  \param forbidden_iter an APT-style iterator over a set
-   *  of versions that should be forbidden.
-   *
-   *  \return \b true if the new solution was not in the closed set.
-   */
-  template<class forbid_iter, class action_iter, class unresolved_iter>
-  bool try_install(const solution &s,
-		   const action_iter &abegin, const action_iter &aend,
-		   const unresolved_iter &ubegin, const unresolved_iter &uend,
-		   const forbid_iter &forbidden_iter)
-  {
-    solution s2=do_install(s, abegin, aend, ubegin, uend,
-			   forbidden_iter);
-    return try_enqueue(s2);
-  }
-
-  /** Generates (and enqueues) successor nodes for the given broken
-   *  dependency of the given solution.
-   *
-   *  \param s the solution to generate successors for
-   *  \param d the dependency to fix
-   *  \param count a value to increment for every successor that
-   *         is enqueued or discarded (as long as it's discarded
-   *         for being too bad, and not for being in closed).
-   */
-  void generate_successors(const solution &s,
-			   const dep &d,
-			   unsigned int &count)
+  /** Try to enqueue the given collection of packages. */
+  void try_enqueue(const std::vector<solution> &sols)
   {
-    version source_version=d.get_source();
-    package source_package=source_version.get_package();
-    version sol_version=s.version_of(source_package);
-    bool found = false;
-
-//     if(d.solvers_begin().end())
-//       {
-// 	std::cout << "No solvers for " << d.get_dep().ParentPkg().Name() 
-// 		  << " " << d.get_dep().DepType() << " "
-// 		  << d.get_dep().TargetPkg().Name();
-// 	if(d.get_dep().TargetVer())
-// 	  std::cout << " (" << d.get_dep().CompType()
-// 		    << " " << d.get_dep().TargetVer() << ")";
-
-// 	if(!d.get_prv().end())
-// 	  std::cout << "  (through "
-// 		    << d.get_prv().OwnerPkg().Name() << " "
-// 		    << d.get_prv().OwnerVer().VerStr()
-// 		    << " Provides "
-// 		    << d.get_prv().ParentPkg().Name()
-// 		    << ")";
-
-// 	std::cout << std::endl;
-//       }
-
-    // if not, then the dep is satisfied!
-    assert(sol_version == source_version);
-
-    // If the source of the dependency was not modified and is
-    // presently installed, try installing a *different version*.
-    if(source_version == source_package.current_version())
-      {
-	for(typename package::version_iterator vi=source_package.versions_begin();
-	    !vi.end(); ++vi)
-	  if(*vi != source_version)
-	    {
-	      if(s.get_forbidden_versions().find(*vi) != s.get_forbidden_versions().end())
-		{
-		  if(debug)
-		    std::cout << "Discarding forbidden version "
-			      << (*vi).get_name() << std::endl;
-
-		  continue;
-		}
+    for(typename std::vector<solution>::const_iterator
+	  succi = sols.begin();	succi != sols.end(); ++succi)
+      try_enqueue(*succi);
+  }
 
-	      if(debug)
-		std::cout << "  Trying to resolve " << d << " by installing " << (*vi).get_package().get_name() << " version " << (*vi).get_name() << std::endl;
+  /** Internal routine to check for the legality of a 'move' and
+   *  generate a conflictor if it's not legal.
+   */
+  bool is_legal(const solution &s,
+		const version &v,
+		action &out_act) const
+  {
+    package p = v.get_package();
+    version cur = p.current_version();
+    version inst = s.version_of(p);
 
-	      found = true;
+    if(inst != cur)
+      {
+	if(debug)
+	  std::cout << "Discarding " << p.get_name() << " "
+		    << v.get_name() << ": monotonicity violation"
+		    << std::endl;
 
-	      action act(*vi, d, s.get_actions().size());
-	      if(try_install(s, &act, (&act)+1,
-			     (dep*)0, (dep*)0,
-			     forbid_iter_builder(d.solvers_begin(), d)))
-		++count;
-	    }
+	out_act.ver = inst;
+	out_act.from_dep_source = false;
+	return false;
       }
-
-    // Enqueue each potential solver.
-    //
-    // (note: skip packages that have already been assigned...I could
-    // probably speed this up very slightly by pushing knowledge down
-    // out of the abstract layer -- no need to iterate over all the
-    // versions of each package that's already assigned)
-    for(typename dep::solver_iterator si=d.solvers_begin();
-	!si.end(); ++si)
+    else
       {
-	version sol_ver=*si;
+	assert(v != cur);
 
-	if(sol_ver != sol_ver.get_package().current_version() &&
-	   s.get_actions().find(sol_ver.get_package())==s.get_actions().end())
-	  {
-	    if(s.get_forbidden_versions().find(sol_ver) != s.get_forbidden_versions().end())
-	      {
-		if(debug)
-		  std::cout << "Discarding forbidden version "
-			    << sol_ver.get_name() << std::endl;
-
-		continue;
-	      }
+	typename std::map<version, dep>::const_iterator found
+	  = s.get_forbidden_versions().find(v);
 
+	if(found == s.get_forbidden_versions().end())
+	  return true;
+	else
+	  {
 	    if(debug)
-	      std::cout << "  Trying to resolve " << d << " by installing " << (*si).get_package().get_name() << " version " << (*si).get_name() << std::endl;
-
-	    found = true;
+	      std::cout << "Discarding " << p.get_name() << " "
+			<< v.get_name() << ": forbidden by the resolution of "
+			<< found->second << std::endl;
+
+	    out_act.ver = s.version_of(v.get_package());
+	    out_act.d   = found->second;
+	    out_act.from_dep_source = true;
 
-	    action act(*si, d, s.get_actions().size());
-	    if(try_install(s, &act, (&act)+1,
-			   (dep*)0, (dep*)0,
-			   dummy_end_iterator<std::pair<version, dep> >()))
-	      ++count;
+	    return false;
 	  }
       }
+  }
 
-    // For soft dependencies, we can optionally leave the dependency
-    // unresolved!
-    if(d.is_soft())
-      {
-	if(debug)
-	  std::cout << "  Trying to ignore soft dependency " << d
-		    << std::endl;
+  /** Internal routine to insert a new conflictor into a conflict set.
+   *  Handles the case in which the conflictor subsumes an existing
+   *  element of the set.
+   */
+  void insert_conflictor(std::map<package, action> &conflict,
+			 const action &act) const
+  {
+    package p = act.ver.get_package();
+    typename std::map<package, action>::const_iterator found
+      = conflict.find(p);
 
-	found = true;
+    if(found == conflict.end())
+      conflict[p]=act;
+    else
+      {
+	action a2 = act;
 
-	int new_score = s.get_score() - broken_score + unfixed_soft_score;
-	// Handle the corner case where this is a full solution.
-	if(s.get_broken().size() == 1)
-	  new_score += full_solution_score;
+	assert(found->second.ver == act.ver);
+	if(a2.from_dep_source)
+	  {
+	    if(found->second.from_dep_source)
+	      assert(a2.d == found->second.d);
 
-	// Only score change is that something goes from being broken
-	// to being permanently unfixed and "soft".
-	if(try_enqueue(solution(s, d,
-				new_score,
-				s.get_action_score())))
-	  ++count;
+	    else
+	      a2.from_dep_source = false;
+	  }
+	conflict[p] = a2;
       }
-
-    // Otherwise we should have discarded this before calling
-    // generate_successors!
-    assert(found);
   }
 
-  /** Generate "forced" successors -- those successors which are
-   *  logically required by the existing dependencies.
-   *
-   *  \return \b true iff the dependency can't be resolved at all
-   *
-   *  \param s the solution to try.
-   *
-   *  \param d the dep to test.
-   *
-   *  \param actions an out parameter -- any generated forced
-   *  successor will be pushed onto this list.
+  /** Convenience routine for the below: try to generate a successor
+   *  by installing a single package version.  NB: assumes that the
+   *  solution's actions have dense identifiers (i.e., less than
+   *  s.get_actions().size()).
    *
-   *  \param toforbid an out parameter -- includes additional
-   *  forbidden versions over what's forbidden by s.
+   *  \param s the solution for which a successor should be generated
+   *  \param v the version to install
+   *  \param d the dependency for which the successor is being generated.
+   *  \param from_dep_source if \b true, this successor is the result
+   *                         of an action on the source of a dependency
+   *  \param conflict a map to which conflictors for this version, if
+   *                  any, will be added.
+   *  \param out a vector onto which the successor will be pushed.
    */
-  bool generate_forced_successor(const solution &s,
+  void generate_single_successor(const solution &s,
 				 const dep &d,
-				 std::set<action> &actions,
-				 std::map<version, dep> &toforbid,
-				 std::set<dep> &toignore)
-  {
-    // As we progress, reasons for forbidding each solver are tossed
-    // into this map.  If the solution is eventaully discarded, this
-    // object will contain a conflict that explains the discard.
-    std::map<package, act_conflict> partial_conflict;
-
-    version v;
-    version source_version=d.get_source();
-    package source_package=source_version.get_package();
-    bool found=false, from_source=false;
-
+				 const version &v,
+				 bool from_dep_source,
+				 std::map<package, action> &conflict,
+				 std::vector<solution> &out) const
+  {
+    action conflictor;
 
     if(debug)
       {
-	std::cout << "Testing whether " << d << " is a forced dependency." << std::endl;
+	std::cout << "Trying to resolve " << d << " by installing "
+		  << v.get_package().get_name() << " "
+		  << v.get_name();
+	if(from_dep_source)
+	  std::cout << " from the dependency source";
+
+	std::cout << std::endl;
       }
 
-    // If the source of the dependency was not modified and is
-    // presently installed, try installing a *different version*.
-    if(source_version == source_package.current_version())
+    const int newid = s.get_actions().size();
+
+    if(!is_legal(s, v, conflictor))
+      insert_conflictor(conflict, conflictor);
+    else
       {
-	for(typename package::version_iterator vi=source_package.versions_begin();
-	    !vi.end(); ++vi)
-	  {
-	    // Ignore the source version, of course.
-	    if(*vi == source_version)
-	      continue;
+	action act(v, d, from_dep_source, newid);
 
-	    typename std::map<version, dep>::const_iterator fi = s.get_forbidden_versions().find(*vi);
-	    if(fi != s.get_forbidden_versions().end())
-	      {
-		// Whoops, it was forbidden in the past.
-		partial_conflict[fi->first.get_package()]
-		  = act_conflict(fi->first, fi->second);
+	solution new_sol = solution::successor(s, &act, &act+1,
+					       (dep *) 0,
+					       (dep *) 0,
+					       universe, weights);
 
-		continue;
-	      }
+	typename std::set<std::map<package, action> >::const_iterator
+	  found = matches_any_conflict(new_sol.get_actions());
 
-	    fi = toforbid.find(*vi);
-	    if(fi != toforbid.end())
+	if(found == conflicts.end())
+	  out.push_back(new_sol);
+	else
+	  {
+	    if(debug)
 	      {
-		partial_conflict[fi->first.get_package()]
-		  = act_conflict(fi->first, fi->second);
-
-		continue;
+		std::cout << "Discarding " << v.get_package().get_name()
+			  << " " << v.get_name() << " due to conflict ";
+		dump_conflict(std::cout, *found);
+		std::cout << std::endl;
 	      }
 
-	    // If there's not essential conflict, check for a
-	    // higher-order conflict.
-	    typename std::set<std::map<package, act_conflict> >::const_iterator
-	      found_conflict = will_conflict(s, act_conflict(*vi, d));
-	    if(found_conflict != conflicts.end())
+	    for(typename std::map<package, action>::const_iterator ci
+		  = found->begin(); ci != found->end(); ++ci)
 	      {
-		// Insert the elements of the new conflict other than
-		// the one that was speculatively inserted.
-		for(typename std::map<package, act_conflict>::const_iterator
-		      fci = found_conflict->begin();
-		    fci != found_conflict->end(); ++fci)
-		  {
-		    if(fci->first != (*vi).get_package())
-		      partial_conflict.insert(*fci);
-		  }
-		continue;
+		// Discard the version that we were trying to install,
+		// so that the caller can use this to form a more
+		// general conflict if all its resolutions fail.
+		if(ci->first != v.get_package())
+		  insert_conflictor(conflict, ci->second);
+		else
+		  assert(ci->second.ver == v);
 	      }
-
-	    // If we already found a legal solver, then we now know
-	    // that we can't force this dep and it's certainly not an
-	    // essential conflict.
-	    if(found)
-	      return false;
-
-	    found = true;
-	    from_source = true;
-	    v = *vi;
 	  }
       }
+  }
+
+  /** Build the successors of a solution node for a particular
+   *  dependency.
+   *
+   *  \param conflict a map which will be initialized with a conflict
+   *  explaining the non-appearance of some solvers (if there are no
+   *  solvers, this will be a full conflict explaining the lack of
+   *  solvers).
+   *
+   *  \param out a vector onto which the successors should be pushed.
+   */
+  void generate_successors(const solution &s,
+			   const dep &d,
+			   std::map<package, action> &conflict,
+			   std::vector<solution> &out) const
+  {
+    version source = d.get_source();
+    typename std::map<package, action>::const_iterator
+      source_found = s.get_actions().find(source.get_package());
+
+    // Try moving the source, if it is legal to do so
+    if(source_found != s.get_actions().end())
+      insert_conflictor(conflict, action(source, d, false, -1));
     else
-      // If the source of the dependency was already installed, the
-      // conflict explanation for not being able to remove it is
-      // itself!
-      partial_conflict[source_package] = act_conflict(source_version);
+      {
+	assert(source == source.get_package().current_version());
 
+	for(typename package::version_iterator vi = source.get_package().versions_begin();
+	    !vi.end(); ++vi)
+	  if(*vi != source)
+	    generate_single_successor(s, d, *vi, true, conflict, out);
+      }
+
+    // Now try installing each target of the dependency.
     for(typename dep::solver_iterator si = d.solvers_begin();
 	!si.end(); ++si)
-      {
-	version sol_ver = *si;
+      generate_single_successor(s, d, *si, false, conflict, out);
 
-	// If the package has already been modified, don't modify it
-	// again.
-	typename std::map<package, action>::const_iterator ai
-	  = s.get_actions().find(sol_ver.get_package());
-	if(ai != s.get_actions().end())
-	    {
-	      typename std::map<package, act_conflict>::iterator pci
-		= partial_conflict.find(ai->first);
+    // Finally, maybe we can leave this dependency unresolved.
+    if(d.is_soft())
+      out.push_back(solution::successor(s, (action *) 0, (action *) 0,
+					&d, &d+1, universe, weights));
+  }
 
-	      if(pci != partial_conflict.end())
-		{
-		  // The partial conflict must contain only stuff
-		  // that's already in this solution.
-		  assert(pci->second.ver == ai->second.ver);
+  /** Processes the given solution by enqueuing its successor nodes
+   *  (if any are available).  Note that for clarity, we now generate
+   *  *all* successors before examining *any*.
+   */
+  void process_solution(const solution &s)
+  {
+    bool forced = false;
+    // Any forcings are immediately applied to 'curr', so that
+    // forcings are performed ASAP.
+    solution curr = s;
+    std::vector<std::pair<dep, std::vector<solution> > > workingq;
+
+    assert(!s.get_broken().empty());
+
+    // This loop attempts to generate all the successors of a
+    // solution.  However, if a "forced" dependency arises, it
+    // re-verifies all the dependencies of the solution.
+    bool done = false;
+    while(!done)
+      {
+	if(debug)
+	  {
+	    std::cout << "Processing ";
+	    s.dump(std::cout);
+	    std::cout << std::endl;
+	  }
 
-		  // Make sure that this is an expansive conflictor.
-		  pci->second.from_dep_source = false;
-		}
-	      else
-		partial_conflict[ai->first] = ai->second.ver;
+	done = true;
 
+	for(typename std::set<dep>::const_iterator bi=s.get_broken().begin();
+	    bi!=s.get_broken().end(); ++bi)
+	  
+	  {
+	    // Check for the case where this dependency has been
+	    // fortuitously solved by forcing another broken
+	    // dependency.
+	    if(s != curr && !(*bi).broken_under(curr))
 	      continue;
-	    }
 
-	if(debug)
-	  std::cout << "sol_ver is " << sol_ver.get_package().get_name() << " " << sol_ver.get_name() << std::endl;
+	    // Assert against impossible conditions (if this happens
+	    // something is broken elsewhere).
+	    if(!(*bi).broken_under(curr))
+	      {
+		std::cerr << "Unexpectedly non-broken dependency "
+			  << *bi << "!" << std::endl;
 
-	// Since we assume the dep is broken, this shouldn't happen!
-	assert(sol_ver != sol_ver.get_package().current_version());
+		version source = (*bi).get_source();
 
-	typename std::map<version, dep>::const_iterator fi = s.get_forbidden_versions().find(*si);
-	if(fi != s.get_forbidden_versions().end())
-	  {
-	    // Whoops, it was forbidden in the past.
-	    partial_conflict[fi->first.get_package()]
-	      = act_conflict(fi->first, fi->second);
+		if(curr.version_of(source.get_package()) != source)
+		  std::cerr << "  (" << source.get_package().get_name()
+			    << " " << source.get_name()
+			    << " is not installed)" << std::endl;
+
+		for(typename dep::solver_iterator si = (*bi).solvers_begin();
+		    !si.end(); ++si)
+		  if(curr.version_of((*si).get_package()) == *si)
+		    std::cerr << "  (" << source.get_package().get_name()
+			      << " " << source.get_name()
+			      << " is not installed)" << std::endl;
 
-	    continue;
-	  }
+		abort();
+	      }
+
+	    std::map<package, action> conflict;
+
+	    workingq.push_back(std::pair<dep, std::vector<solution> >(*bi, std::vector<solution>()));
+	    generate_successors(curr, *bi, conflict, workingq.back().second);
 
-	fi = toforbid.find(*si);
-	if(fi != toforbid.end())
-	  {
-	    partial_conflict[fi->first.get_package()]
-	      = act_conflict(fi->first, fi->second);
 
-	    continue;
-	  }
 
-	// If there's not essential conflict, check for a
-	// higher-order conflict.
-	typename std::set<std::map<package, act_conflict> >::const_iterator
-	  found_conflict = will_conflict(s, act_conflict(*si));
-	if(found_conflict != conflicts.end())
-	  {
-	    // Insert the elements of the new conflict other than
-	    // the one that was speculatively inserted.
-	    for(typename std::map<package, act_conflict>::const_iterator
-		  fci = found_conflict->begin();
-		fci != found_conflict->end(); ++fci)
+	    int sz = workingq.back().second.size();
+
+	    if(sz == 0)
 	      {
-		if(fci->first != (*si).get_package())
-		  partial_conflict.insert(*fci);
-	      }
+		if(debug)
+		  {
+		    std::cout << "Discarding solution; unresolvable dependency "
+			      << *bi << " with conflict ";
 
-	    partial_conflict.insert(found_conflict->begin(),
-				    found_conflict->end());
-	    continue;
-	  }
+		    dump_conflict(std::cout, conflict);
 
-	if(found)
-	  return false;
-	found=true;
-	v=sol_ver;
-      }
+		    std::cout << std::endl;
+		  }
 
-    if(found)
-      {
-	// Soft dependencies shouldn't be forced, as you have the
-	// option of discarding them.
-	if(d.is_soft())
-	  return false;
+		add_conflict(conflict);
 
-	if(debug)
-	    std::cout << "Forced resolution of " << d
-		      << " by installing " << v.get_package().get_name()
-		      << " version " << v.get_name() << std::endl;
-
-	if(from_source)
-	  for(typename dep::solver_iterator si=d.solvers_begin();
-	      !si.end(); ++si)
-	    toforbid[*si] = d;
+		return;
+	      }
+	    else if(sz == 1)
+	      {
+		if(debug)
+		  std::cout << "Forced resolution of " << *bi << std::endl;
 
-	actions.insert(action(v, d, s.get_actions().size()+actions.size()));
+		curr = workingq.back().second.front();
+		forced = true;
+		done = false;
+	      }
+	  }
+      };
 
-	return false;
-      }
+    if(forced)
+      try_enqueue(curr);
     else
       {
-	// If there really is no choice, then soft deps can be left
-	// unresolved.
-	if(d.is_soft())
-	  {
-	    if(debug)
-	      std::cout << "Forced soft dependency " << d
-			<< " to be left unresolved." << std::endl;
+	unsigned int nsols = 0;
 
-	    toignore.insert(d);
+	// First try to enqueue stuff related to a dependency that the
+	// user constrained; then just go for a free-for-all.
 
-	    return false;
-	  }
-	else
-	  {
-	    if(debug)
-	      {
-		std::cout << "Discarding solution: unsolvable dependency " << d
-			  << " with new conflict ";
-		dump(std::cout, partial_conflict);
+	for(typename std::vector<std::pair<dep, std::vector<solution> > >::const_iterator
+	      wqi = workingq.begin();
+	    wqi != workingq.end() && (nsols == 0 ||
+				      nsols < max_successors);
+	    ++wqi)
+	  if(impinges_user_constraint(wqi->first))
+	    {
+	      if(debug)
+		std::cout << "Generating successors for " << wqi->first
+			  << std::endl;
 
-		std::cout << std::endl;
+	      try_enqueue(wqi->second);
+	      nsols += wqi->second.size();
+	    }
 
-		conflicts.insert(partial_conflict);
-	      }
+	for(typename std::vector<std::pair<dep, std::vector<solution> > >::const_iterator
+	      wqi = workingq.begin();
+	    wqi != workingq.end() && (nsols == 0 ||
+				      nsols < max_successors);
+	    ++wqi)
+	  {
+	    if(debug)
+	      std::cout << "Generating successors for " << wqi->first
+			<< std::endl;
 
-	    return true;
+	    try_enqueue(wqi->second);
+	    nsols += wqi->second.size();
 	  }
       }
   }
-
-  /** Processes the given solution by enqueuing its successor nodes
-   *  (if any are available).
-   */
-  void process_solution(const solution &s)
-  {
-    // How many solutions have been generated?
-    unsigned int sols=0;
-
-    std::set<action> forced_actions;
-    std::map<version, dep> forced_forbidden_versions;
-    std::set<dep> forced_unresolved_soft_deps;
-
-
-    for(typename std::set<dep>::const_iterator bi=s.get_broken().begin();
-	bi!=s.get_broken().end(); ++bi)
-      if(generate_forced_successor(s, *bi,
-				   forced_actions,
-				   forced_forbidden_versions,
-				   forced_unresolved_soft_deps))
-	return;
-
-    if(!forced_actions.empty() || !forced_unresolved_soft_deps.empty())
-      {
-	try_install(s,
-		    forced_actions.begin(), forced_actions.end(),
-		    forced_unresolved_soft_deps.begin(), forced_unresolved_soft_deps.end(),
-		    apt_iter_wrapper(forced_forbidden_versions.begin(), forced_forbidden_versions.end()));
-	return;
-      }
-
-    // First, try to generate successors for the broken dependencies
-    // that are affected by user constraints (branching these off ASAP
-    // lets us keep the branching factor low and converge faster).
-    for(typename std::set<dep>::const_iterator bi=s.get_broken().begin();
-	bi!=s.get_broken().end() && (sols==0 || sols<max_successors); ++bi)
-      if(impinges_user_constraint(*bi))
-	generate_successors(s, *bi, sols);
-
-
-    for(typename std::set<dep>::const_iterator bi=s.get_broken().begin();
-	bi!=s.get_broken().end() && (sols==0 || sols<max_successors); ++bi)
-      generate_successors(s, *bi, sols);
-  }
 public:
 
   /** Construct a new generic_problem_resolver.
@@ -2030,16 +1768,11 @@
 			   int infinity, unsigned int _max_successors,
 			   int _full_solution_score,
 			   const PackageUniverse &_universe)
-    :step_score(_step_score), broken_score(_broken_score),
-     unfixed_soft_score(_unfixed_soft_score),
+    :weights(_step_score, _broken_score, _unfixed_soft_score,
+	     _full_solution_score, _universe.get_version_count()),
      minimum_score(-infinity), max_successors(_max_successors),
-     full_solution_score(_full_solution_score),
      universe(_universe), finished(false), deferred_dirty(false), debug(false), remove_stupid(true)
   {
-    version_scores=new int[universe.get_version_count()];
-    for(size_t i=0; i<universe.get_version_count(); ++i)
-      version_scores[i]=0;
-
     // Find all the broken deps.
     for(typename PackageUniverse::broken_dep_iterator bi=universe.broken_begin();
 	!bi.end(); ++bi)
@@ -2048,7 +1781,6 @@
 
   ~generic_problem_resolver()
   {
-    delete[] version_scores;
   }
 
   const PackageUniverse &get_universe()
@@ -2056,12 +1788,12 @@
     return universe;
   }
 
-  int get_step_score() {return step_score;}
-  int get_broken_score() {return broken_score;}
-  int get_unresolved_soft_dep_score() {return unfixed_soft_score;}
+  int get_step_score() {return weights.step_score;}
+  int get_broken_score() {return weights.broken_score;}
+  int get_unresolved_soft_dep_score() {return weights.unfixed_soft_score;}
   int get_infinity() {return -minimum_score;}
   int get_max_successors() {return max_successors;}
-  int get_full_solution_score() {return full_solution_score;}
+  int get_full_solution_score() {return weights.full_solution_score;}
 
   /** Enables or disables debugging.  Debugging is initially
    *  disabled.
@@ -2090,7 +1822,7 @@
     closed.clear();
 
     for(size_t i=0; i<universe.get_version_count(); ++i)
-      version_scores[i]=0;
+      weights.version_scores[i]=0;
   }
 
   /** \return \b true if no solutions have been examined yet.
@@ -2121,7 +1853,7 @@
   void set_version_score(const version &ver, int score)
   {
     assert(ver.get_id()<universe.get_version_count());
-    version_scores[ver.get_id()]=score;
+    weights.version_scores[ver.get_id()]=score;
   }
 
   /** As set_version_score, but instead of replacing the current score
@@ -2130,14 +1862,14 @@
   void add_version_score(const version &ver, int score)
   {
     assert(ver.get_id()<universe.get_version_count());
-    version_scores[ver.get_id()]+=score;
+    weights.version_scores[ver.get_id()]+=score;
   }
 
   /** \return the score of the version ver. */
   int get_version_score(const version &ver)
   {
     assert(ver.get_id()<universe.get_version_count());
-    return version_scores[ver.get_id()];
+    return weights.version_scores[ver.get_id()];
   }
 
   /** Reject future solutions containing this version.
@@ -2284,7 +2016,9 @@
       {
 	closed.clear();
 
-	open.push(solution(initial_broken, initial_broken.size()*broken_score));
+	open.push(solution::root_node(initial_broken,
+				      universe,
+				      weights));
       }
 
     while(max_steps>0 && !open.empty())
@@ -2320,13 +2054,6 @@
 
 	closed.insert(s);
 
-	if(debug)
-	  {
-	    std::cout << "Processing ";
-	    s.dump(std::cout);
-	    std::cout << std::endl;
-	  }
-
 	// If all dependencies are satisfied, we found a solution.
 	if(s.is_full_solution())
 	  {
@@ -2366,7 +2093,14 @@
 		generated_solutions.push_back(minimized);
 
 		if(debug)
-		  std::cout << " *** Converged after " << odometer << " steps." << std::endl;
+		  {
+		    std::cout << " *** Converged after " << odometer << " steps." << std::endl;
+		    std::cout << " *** open: " << open.size()
+			      << "; closed: " << closed.size()
+			      << "; conflicts: " << conflicts.size()
+			      << "; deferred: " << deferred.size()
+			      << "; generated solutions: " << generated_solutions.size();
+		  }
 
 		return minimized;
 	      }
@@ -2391,7 +2125,14 @@
     finished=true;
 
     if(debug)
-      std::cout << " *** Out of solutions after " << odometer << " steps." << std::endl;
+      {
+	std::cout << " *** Out of solutions after " << odometer << " steps." << std::endl;
+	std::cout << " *** open: " << open.size()
+		  << "; closed: " << closed.size()
+		  << "; conflicts: " << conflicts.size()
+		  << "; deferred: " << deferred.size()
+		  << "; generated solutions: " << generated_solutions.size();
+      }
 
     throw NoMoreSolutions();
   }
@@ -2406,7 +2147,7 @@
 
 	for(typename PackageUniverse::package::version_iterator j=(*i).versions_begin();
 	    !j.end(); ++j)
-	  if(version_scores[(*j).get_id()]!=0)
+	  if(weights.version_scores[(*j).get_id()]!=0)
 	    any_modified=true;
 
 	if(any_modified)
@@ -2415,8 +2156,8 @@
 
 	    for(typename PackageUniverse::package::version_iterator j=(*i).versions_begin();
 	    !j.end(); ++j)
-	      if(version_scores[(*j).get_id()]!=0)
-		out << " " << (*j).get_name() << " " << version_scores[(*j).get_id()];
+	      if(weights.version_scores[(*j).get_id()]!=0)
+		out << " " << (*j).get_name() << " " << weights.version_scores[(*j).get_id()];
 
 	    out << " >" << std::endl;
 	  }
@@ -2426,30 +2167,7 @@
 };
 
 template<class PackageUniverse>
-inline
-std::ostream &operator<<(std::ostream &out, const typename std::map<typename PackageUniverse::package, typename generic_problem_resolver<PackageUniverse>::act_conflict> &ac)
-{
-  out << "(";
-
-  for(typename std::map<typename PackageUniverse::package, typename generic_problem_resolver<PackageUniverse>::act_conflict>::const_iterator ci
-	= ac.begin(); ci != ac.end(); ++ci)
-    {
-      if(ci != ci->begin())
-	out << ", ";
-      out << ci->first.name() << " "
-	  << ci->second.ver.name();
-
-      if(ci->second.from_dep_source)
-	out << " [" << ci->second.d << "]";
-    }
-
-  out << ")";
-
-  return out;
-}
-
-template<class PackageUniverse>
-std::ostream &generic_problem_resolver<PackageUniverse>::dump(std::ostream &out, const typename generic_problem_resolver<PackageUniverse>::act_conflict &act) const
+std::ostream &generic_problem_resolver<PackageUniverse>::dump_conflict(std::ostream &out, const typename generic_problem_resolver<PackageUniverse>::action &act) const
 {
   out << act.ver.get_package().get_name() << " "
       << act.ver.get_name();
@@ -2461,17 +2179,17 @@
 }
 
 template<class PackageUniverse>
-std::ostream &generic_problem_resolver<PackageUniverse>::dump(std::ostream &out, const typename std::map<typename PackageUniverse::package, typename generic_problem_resolver<PackageUniverse>::act_conflict> &act) const
+std::ostream &generic_problem_resolver<PackageUniverse>::dump_conflict(std::ostream &out, const typename std::map<typename PackageUniverse::package, typename generic_solution<PackageUniverse>::action> &act) const
 {
   out << "(";
 
-  for(typename std::map<typename PackageUniverse::package, typename generic_problem_resolver<PackageUniverse>::act_conflict>::const_iterator ci
+  for(typename std::map<typename PackageUniverse::package, typename generic_solution<PackageUniverse>::action>::const_iterator ci
 	= act.begin(); ci != act.end(); ++ci)
     {
       if(ci != act.begin())
 	out << ", ";
 
-      dump(out, ci->second);
+      dump_conflict(out, ci->second);
     }
 
   out << ")";

Modified: branches/aptitude-0.3/aptitude/src/generic/problemresolver/solution.h
==============================================================================
--- branches/aptitude-0.3/aptitude/src/generic/problemresolver/solution.h	(original)
+++ branches/aptitude-0.3/aptitude/src/generic/problemresolver/solution.h	Wed Aug 24 23:38:47 2005
@@ -26,6 +26,46 @@
 #include <map>
 #include <set>
 
+/** Represents the current score weights for a resolver.  Used to
+ *  calculate scores at the time a solution is instantiated.
+ */
+struct solution_weights
+{
+  /** How much to reward long and/or broken solutions.  Typically
+   *  negative to penalize such things, or 0 to ignore them.
+   */
+  int step_score, broken_score, unfixed_soft_score;
+
+
+  /** How much to reward real solutions -- solutions that fix all
+   *  dependencies.  Make this big to immediately pick them up, or
+   *  small to ignore "bad" solutions (at the risk of running out of
+   *  time if no better solution pops up).
+   */
+  int full_solution_score;
+
+  /** The scores to apply to individual package versions.
+   */
+  int *version_scores;
+
+  solution_weights(int _step_score, int _broken_score,
+		   int _unfixed_soft_score, int _full_solution_score,
+		   unsigned long num_versions)
+    :step_score(_step_score), broken_score(_broken_score),
+     unfixed_soft_score(_unfixed_soft_score),
+     full_solution_score(_full_solution_score),
+     version_scores(new int[num_versions])
+  {
+    for(unsigned long i = 0; i < num_versions; ++i)
+      version_scores[i] = 0;
+  }
+
+  ~solution_weights()
+  {
+    delete[] version_scores;
+  }
+};
+
 /** Represents a partial or complete solution to a dependency
  *  problem.  Solutions are transparently refcounted to save on
  *  memory and avoid copies.
@@ -56,16 +96,26 @@
     /** The dependency that triggered this action. */
     dep d;
 
+    /** If \b true, this action was triggered by removing the source
+     *	of the dependency d.
+     */
+    bool from_dep_source:1;
+
     /** The order in which this action should be placed.  Used when
      *  presenting a "story" about a solution.
      */
-    int id;
+    int id:31;
 
     action() {}
 
     action(const version &_ver,
 	   const dep &_d,
-	   int _id):ver(_ver), d(_d), id(_id) {}
+	   bool _from_dep_source,
+	   int _id)
+      : ver(_ver), d(_d),
+	from_dep_source(_from_dep_source), id(_id)
+    {
+    }
 
     bool operator<(const action &other) const {return ver<other.ver;}
 
@@ -131,107 +181,20 @@
     void incref() const {++refcount;}
     void decref() const {assert(refcount>0); if(--refcount==0) delete this;}
 
-    /** Construct a new solution_rep by taking the given solution and
-     *  moving a single dependency into the set of unresolved soft
-     *  deps.
-     */
-    solution_rep(const generic_solution &parent,
-		 const dep &ignore,
-		 int _score, int _action_score)
-      :actions(parent.get_actions()),
-       broken_deps(parent.get_broken()),
-       unresolved_soft_deps(parent.get_unresolved_soft_deps()),
-       score(_score), action_score(_action_score), refcount(1)
-    {
-      assert(broken_deps.find(ignore) != broken_deps.end());
-
-      broken_deps.erase(ignore);
-      unresolved_soft_deps.insert(ignore);
-    }
-
-    /** Construct a new solution_rep.  The initial reference count
-     *  is \b 1.
-     *
-     *  \param ver the version to install beyond what is in the parent
-     *  \param parent the parent solution
-     *  \param _broken_deps the dependencies that are broken in this partial solution
-     *  \param _score the score of this solution
-     *  \param forbidden_iter an APT-style iterator over the set of
-     *         newly forbidden versions as (version, reason) pairs.
-     *  \param _action_score the score not due to broken deps
-     */
-    template<class forbid_iter, class action_iter, class unresolved_iter>
-    solution_rep(const action_iter &abegin,
-		 const action_iter &aend,
-		 const generic_solution &parent,
-		 const unresolved_iter &ubegin,
-		 const unresolved_iter &uend,
+    /** Construct a new solution_rep directly. */
+    solution_rep(const std::map<package, action> &_actions,
 		 const std::set<dep> &_broken_deps,
-		 const forbid_iter &forbidden_iter,
-		 int _score, int _action_score)
-      :actions(parent.get_actions()), broken_deps(_broken_deps),
-       unresolved_soft_deps(parent.get_unresolved_soft_deps()),
-       forbidden_versions(parent.get_forbidden_versions()),
-       score(_score), action_score(_action_score),
-       refcount(1)
-    {
-      for(forbid_iter i=forbidden_iter; !i.end(); ++i)
-	forbidden_versions.insert(*i);
-
-      for(action_iter a=abegin; a!=aend; ++a)
-	{
-	  assert(actions.find(a->ver.get_package()) == actions.end());
-	  actions[a->ver.get_package()] = *a;
-	}
-
-      for(unresolved_iter u = ubegin; u != uend; ++u)
-	{
-	  assert(broken_deps.find(*u) == broken_deps.end());
-	  unresolved_soft_deps.insert(*u);
-	}
-    }
-
-    /** Construct a new solution_rep.  The initial reference count is
-     *  \b 1.
-     *
-     *  \param abegin the start of a list of actions to perform
-     *  \param aend   the end ot the list of actions to perform
-     *  \param _broken_deps the dependencies that are broken in this partial solution
-     *  \param _score the score of this solution
-     *  \param forbidden_iter an APT-style iterator over the set of
-     *         newly forbidden versions as (version, reason) pairs
-     *  \param _action_score the score not due to broken deps
-     */
-    template<class forbid_iter, class action_iter>
-    solution_rep(const action_iter &abegin,
-		 const action_iter &aend,
 		 const std::set<dep> &_unresolved_soft_deps,
-		 const std::set<dep> &_broken_deps,
-		 const forbid_iter &forbidden_iter,
-		 int _score, int _action_score)
-      :broken_deps(_broken_deps),
-       unresolved_soft_deps(_unresolved_soft_deps),
-       score(_score), action_score(_action_score),
-       refcount(1)
-    {
-      for(forbid_iter i=forbidden_iter; !i.end(); ++i)
-	forbidden_versions.insert(*i);
-
-      for(action_iter a = abegin; a != aend; ++a)
-	{
-	  assert(actions.find(a->ver.get_package()) == actions.end());
-	  actions[a->ver.get_package()] = *a;
-	}
-    }
-
-    /** Construct a solution with \b no action, the given set of
-     *  broken dependencies, and the given score.  Used to generate
-     *  the root node of the search.
-     */
-    solution_rep(const std::set<dep> &_broken_deps,
-		 int _score)
-      :broken_deps(_broken_deps), score(_score),
-       action_score(0), refcount(1)
+		 const std::map<version, dep> &_forbidden_versions,
+		 int _score,
+		 int _action_score)
+      : actions(_actions),
+	broken_deps(_broken_deps),
+	unresolved_soft_deps(_unresolved_soft_deps),
+	forbidden_versions(_forbidden_versions),
+	score(_score),
+	action_score(_action_score),
+	refcount(1)
     {
     }
 
@@ -275,76 +238,42 @@
   }; // End solution representation.
 
   solution_rep *real_soln;
-public:
-  generic_solution():real_soln(0) {}
 
-  /** Create a solution. */
-  generic_solution(const generic_solution &parent,
-		   const dep &ignore,
-		   int score, int action_score)
-    :real_soln(new solution_rep(parent, ignore, score, action_score))
-  {
-  }
-
-  /** Create a solution.
-   *
-   * \param [abegin,aend) the new actions to enqueue
-   *
-   * \param parent the parent of this solution
-   *
-   * \param forbidden_iter an APT-style iterator (i.e., with an end()
-   * method) over the set of newly forbidden versions.
-   *
-   * \param score the total score of this new solution
-   *
-   * \param action_score the portion of score due to actions
+  /** Create a solution directly from a rep; assumes control of the
+   *  reference passed in as an argument (i.e., doesn't incref() it)
    */
-  template<class forbid_iter, class action_iter, class unresolved_iter>
-  generic_solution(const action_iter &abegin,
-		   const action_iter &aend,
-		   const generic_solution &parent,
-		   const unresolved_iter &ubegin,
-		   const unresolved_iter &uend,
-		   const std::set<dep> &broken_deps,
-		   const forbid_iter &forbidden_iter,
-		   int score, int action_score)
-    :real_soln(new solution_rep(abegin, aend, parent,
-				ubegin, uend, broken_deps,
-				forbidden_iter, score, action_score))
+  generic_solution(solution_rep *r)
+    :real_soln(r)
   {
   }
 
-  /** Create a solution.
-   *
-   * \param [abegin,aend) the new actions to enqueue
-   *
-   * \param forbidden_iter an APT-style iterator (i.e., with an end()
-   * method) over the set of newly forbidden versions.
-   *
-   * \param score the total score of this new solution
-   *
-   * \param action_score the portion of score due to actions
+  /** Wrapper structure used to pass a raw map into broken_under().
+   *  Used to determine the set of packages broken by a solution
+   *  before the solution is actually created.
    */
-  template<class forbid_iter, class action_iter>
-  generic_solution(const action_iter &abegin,
-		   const action_iter &aend,
-		   const std::set<dep> &unresolved_soft_deps,
-		   const std::set<dep> &broken_deps,
-		   const forbid_iter &forbidden_iter,
-		   int score, int action_score)
-    :real_soln(new solution_rep(abegin, aend,
-				unresolved_soft_deps,
-				broken_deps,
-				forbidden_iter,
-				score, action_score))
+  struct solution_map_wrapper
   {
-  }
+    const std::map<package, action> &actions;
+  public:
+    solution_map_wrapper(const std::map<package, action> &_actions)
+      :actions(_actions)
+    {
+    }
 
-  generic_solution(const std::set<dep> &broken_deps,
-		   int score)
-    :real_soln(new solution_rep(broken_deps, score))
-  {
-  }
+    version version_of(const package &p) const
+    {
+      typename std::map<package, action>::const_iterator found
+	= actions.find(p);
+
+      if(found != actions.end())
+	return found->second.ver;
+      else
+	return p.current_version();
+    }
+  };
+
+public:
+  generic_solution():real_soln(0) {}
 
   generic_solution(const generic_solution &other)
     :real_soln(other.real_soln)
@@ -353,6 +282,132 @@
       real_soln->incref();
   }
 
+  /** Generate the root node for a search in the given universe. */
+  static generic_solution root_node(const std::set<dep> &initial_broken,
+				    const PackageUniverse &universe,
+				    const solution_weights &weights)
+  {
+    int score = initial_broken.size() * weights.broken_score;
+
+    return generic_solution(new solution_rep(std::map<package, action>(),
+					     initial_broken,
+					     std::set<dep>(),
+					     std::map<version, dep>(),
+					     score,
+					     0));
+  }
+
+  /** Generate a successor to the given solution.
+   *
+   *  \param [abegin, aend) a range of actions to perform
+   *  \param [ubegin, uend) a range of dependencies to leave unresolved
+   */
+  template<typename a_iter, typename u_iter>
+  static generic_solution successor(const generic_solution &s,
+				    const a_iter &abegin,
+				    const a_iter &aend,
+				    const u_iter &ubegin,
+				    const u_iter &uend,
+				    const PackageUniverse &universe,
+				    const solution_weights &weights)
+  {
+    // NB: as I fully expect to move to a scheme of shared-memory
+    // sets/maps, the implicit copies here will go away eventually.
+    std::set<dep> broken_deps = s.get_broken();
+    std::map<package, action> actions = s.get_actions();
+    std::map<version, dep> forbidden_versions = s.get_forbidden_versions();
+    std::set<dep> unresolved_soft_deps = s.get_unresolved_soft_deps();
+    int action_score = s.get_action_score();
+
+    for(a_iter ai = abegin; ai != aend; ++ai)
+      {
+	const action &a = *ai;
+	assert(actions.find(a.ver.get_package()) == actions.end());
+	assert(a.ver != a.ver.get_package().current_version());
+
+	actions[a.ver.get_package()] = a;
+
+	action_score += weights.step_score;
+	action_score += weights.version_scores[a.ver.get_id()];
+	action_score -= weights.version_scores[a.ver.get_package().current_version().get_id()];
+
+	if(a.from_dep_source)
+	  {
+	    for(typename dep::solver_iterator si = a.d.solvers_begin();
+		!si.end(); ++si)
+	      forbidden_versions[*si] = a.d;
+	  }
+
+
+
+	// Update the set of broken dependencies, trying to re-use as
+	// much of the former set as possible.
+	version old_version=s.version_of(a.ver.get_package());
+	solution_map_wrapper tmpsol(actions);
+
+	// Check reverse deps of the old version
+	for(typename version::revdep_iterator rd=old_version.revdeps_begin();
+	    !rd.end(); ++rd)
+	  if(unresolved_soft_deps.find(*rd) == unresolved_soft_deps.end() &&
+	     (*rd).broken_under(tmpsol))
+	    broken_deps.insert(*rd);
+	  else
+	    broken_deps.erase(*rd);
+
+	// Check reverse deps of the new version
+	//
+	// Because reverse deps of a version might be fixed by its
+	// removal, we need to check brokenness and insert or erase as
+	// appropriate.
+	for(typename version::revdep_iterator rd = a.ver.revdeps_begin();
+	    !rd.end(); ++rd)
+	  if(unresolved_soft_deps.find(*rd) == unresolved_soft_deps.end() &&
+	     (*rd).broken_under(tmpsol))
+	    broken_deps.insert(*rd);
+	  else
+	    broken_deps.erase(*rd);
+
+	// Remove all forward deps of the old version (they're
+	// automagically fixed)
+	for(typename version::dep_iterator di=old_version.deps_begin();
+	    !di.end(); ++di)
+	  broken_deps.erase(*di);
+
+	// Check forward deps of the new version (no need to erase
+	// non-broken dependencies since they're automatically
+	// non-broken at the start)
+	for(typename version::dep_iterator di=a.ver.deps_begin();
+	    !di.end(); ++di)
+	  if(unresolved_soft_deps.find(*di) == unresolved_soft_deps.end() &&
+	     (*di).broken_under(tmpsol))
+	    broken_deps.insert(*di);
+      }
+
+    // Add notes about unresolved dependencies
+    for(u_iter ui = ubegin; ui != uend; ++ui)
+      {
+	const dep &d = *ui;
+
+	typename std::set<dep>::const_iterator found
+	  = broken_deps.find(d);
+
+	assert(found != broken_deps.end());
+	broken_deps.erase(found);
+	unresolved_soft_deps.insert(d);
+      }
+
+    int score
+      = action_score + broken_deps.size()*weights.broken_score
+      + unresolved_soft_deps.size()*weights.unfixed_soft_score;
+
+    return generic_solution(new solution_rep(actions,
+					     broken_deps,
+					     unresolved_soft_deps,
+					     forbidden_versions,
+					     score,
+					     action_score));
+  }
+
   ~generic_solution()
   {
     if(real_soln)

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	Wed Aug 24 23:38:47 2005
@@ -107,15 +107,15 @@
     dummy_universe::dep d2 = *di;
 
     dummy_solution::action a1(u.find_package("a").version_from_name("v1"),
-			      d1, 49);
+			      d1, false, 49);
     dummy_solution::action a2(u.find_package("a").version_from_name("v1"),
-			      d2, 21);
+			      d2, false, 21);
 
     assertEqEquivalent(a1, a1);
     assertEqEquivalent(a1, a2);
 
     dummy_solution::action a3(u.find_package("a").version_from_name("v2"),
-			      d1, 49);
+			      d1, false, 49);
 
     assertEqEquivalent(a2, a2);
     assertEqEquivalent(a3, a3);
@@ -124,7 +124,7 @@
     assertEqInequivalent(a2, a3);
 
     dummy_solution::action a4(u.find_package("c").version_from_name("v3"),
-			      d2, 21);
+			      d2, false, 21);
 
     assertEqEquivalent(a4, a4);
     assertEqInequivalent(a1, a4);
@@ -145,67 +145,68 @@
     CPPUNIT_ASSERT(!di.end());
     dummy_universe::dep d2 = *di;
 
+    solution_weights weights(0, 0, 0, 0, u.get_version_count());
+
     std::set<dummy_universe::dep> u_broken;
     for(dummy_universe::broken_dep_iterator bi = u.broken_begin();
 	!bi.end(); ++bi)
       u_broken.insert(*bi);
 
     dummy_solution::action a1(u.find_package("a").version_from_name("v1"),
-			      d1, 49);
+			      d1, false, 49);
     dummy_solution::action a2(u.find_package("a").version_from_name("v1"),
-			      d2, 21);
+			      d2, false, 21);
     dummy_solution::action a3(u.find_package("a").version_from_name("v2"),
-			      d1, 49);
+			      d1, false, 49);
 
     dummy_solution::action a4(u.find_package("c").version_from_name("v3"),
-			      d2, 21);
+			      d2, false, 21);
 
 
     // Generate some meaningless solutions to check that equivalency
     // is correctly calculated according to the version mappings and
     // the set of unsolved soft deps.
-    dummy_solution s0(u_broken, 0);
-    dummy_solution s1(&a1, &a1+1, s0,
-		      (dummy_universe::dep *) 0,
-		      (dummy_universe::dep *) 0,
-		      s0.get_broken(),
-		      dummy_end_iterator<dummy_universe::version>(),
-		      0, 0);
-    dummy_solution s2(&a2, &a2+1, s0,
-		      (dummy_universe::dep *) 0,
-		      (dummy_universe::dep *) 0,
-		      s0.get_broken(),
-		      dummy_end_iterator<dummy_universe::version>(),
-		      0, 0);
-    dummy_solution s3(&a3, &a3+1, s0,
-		      (dummy_universe::dep *) 0,
-		      (dummy_universe::dep *) 0,
-		      s0.get_broken(),
-		      dummy_end_iterator<dummy_universe::version>(),
-		      0, 0);
-    dummy_solution s4(&a4, &a4+1, s0,
-		      (dummy_universe::dep *) 0,
-		      (dummy_universe::dep *) 0,
-		      s0.get_broken(),
-		      dummy_end_iterator<dummy_universe::version>(),
-		      0, 0);
+    dummy_solution s0 = dummy_solution::root_node(u_broken,
+						  u, weights);
+    dummy_solution s1
+      = dummy_solution::successor(s0, &a1, &a1+1,
+				  (dummy_universe::dep *) 0,
+				  (dummy_universe::dep *) 0,
+				  u, weights);
+    dummy_solution s2
+      = dummy_solution::successor(s0, &a2, &a2+1,
+				  (dummy_universe::dep *) 0,
+				  (dummy_universe::dep *) 0,
+				  u, weights);
+    dummy_solution s3
+      = dummy_solution::successor(s0, &a3, &a3+1,
+				  (dummy_universe::dep *) 0,
+				  (dummy_universe::dep *) 0,
+				  u, weights);
+    dummy_solution s4
+      = dummy_solution::successor(s0, &a4, &a4+1,
+				  (dummy_universe::dep *) 0,
+				  (dummy_universe::dep *) 0,
+				  u, weights);
 
     // the following two should be equal.
-    dummy_solution s5(&a4, &a4+1, s1,
-		      (dummy_universe::dep *) 0,
-		      (dummy_universe::dep *) 0,
-		      s1.get_broken(),
-		      dummy_end_iterator<dummy_universe::version>(),
-		      0, 0);
-    dummy_solution s6(&a1, &a1+1, s4,
-		      (dummy_universe::dep *) 0,
-		      (dummy_universe::dep *) 0,
-		      s4.get_broken(),
-		      dummy_end_iterator<dummy_universe::version>(),
-		      0, 0);
+    dummy_solution s5
+      = dummy_solution::successor(s1, &a4, &a4+1,
+				  (dummy_universe::dep *) 0,
+				  (dummy_universe::dep *) 0,
+				  u, weights);
+    dummy_solution s6
+      = dummy_solution::successor(s4, &a1, &a1+1,
+				  (dummy_universe::dep *) 0,
+				  (dummy_universe::dep *) 0,
+				  u, weights);
 
     // and this should not equal any other solution.
-    dummy_solution s7(s0, d1, 0, 0);
+    dummy_solution s7
+      = dummy_solution::successor(s0,
+				  (dummy_solution::action *) 0,
+				  (dummy_solution::action *) 0,
+				  &d1, &d1+1, u, weights);
 
     dummy_resolver::solution_contents_compare solcmp;
 



More information about the Aptitude-svn-commit mailing list