def find_solution(solution_constraints, valid_packages = nil)
      # this is a performance optimization so that packages that are
      # completely unreachable by the solution constraints don't get
      # added to the CSP
      packages_to_include_in_solve = trim_unreachable_packages(dep_graph, solution_constraints)

      begin
        # first, try to solve the whole set of constraints
        solve(dep_graph.clone, solution_constraints, valid_packages, packages_to_include_in_solve)
      rescue Exceptions::NoSolutionFound, Exceptions::TimeBoundExceededNoSolution
        # since we're here, solving the whole system failed, so add
        # the solution_constraints one-by-one and try to solve in
        # order to find the constraint that breaks the system in order
        # to give helpful debugging info
        #
        # TODO [cw,2010/11/28]: for an efficiency gain, instead of
        # continually re-building the problem and looking for a
        # solution, turn solution_constraints into a Generator and
        # iteratively add and solve in order to re-use
        # propagations. This will require separating setting up the
        # constraints from searching for the solution.
        Timeout::timeout(@time_bound, Exceptions::TimeBoundExceededNoSolution) do
          solution_constraints.each_index do |idx|
            workspace = dep_graph.clone
            begin
              solve(workspace, solution_constraints[0..idx], valid_packages, packages_to_include_in_solve)
            rescue Exceptions::NoSolutionFound => nsf
              disabled_packages =
                packages_to_include_in_solve.inject([]) do |acc, elt|
                pkg = workspace.package(elt.name)
                acc << pkg if nsf.unsatisfiable_problem.is_package_disabled?(pkg.gecode_package_id)
                acc
              end
              # disambiguate between packages disabled becuase they
              # don't exist and those that have otherwise problematic
              # constraints
              disabled_non_existent_packages = []
              disabled_most_constrained_packages = []
              disabled_packages.each do |disabled_pkg|
                disabled_collection =
                  if disabled_pkg.valid? || (valid_packages && valid_packages.include?(disabled_pkg))
                    disabled_most_constrained_packages
                  else
                    disabled_non_existent_packages
                  end
                disabled_collection << disabled_pkg
              end

              # Pick the first non-existent or most-constrained package
              # that was required or the package whose constraints had
              # to be disabled in order to find a solution and generate
              # feedback for it. We only report feedback for one
              # package, because it is in fact actionable and dispalying
              # feedback for every disabled package would probably be
              # too long. The full set of disabled packages is
              # accessible in the NoSolutionExists exception.
              disabled_package_to_report_on = disabled_non_existent_packages.first ||
                disabled_most_constrained_packages.first
              feedback = error_reporter.give_feedback(dep_graph, solution_constraints, idx,
                                                      disabled_package_to_report_on)

              raise Exceptions::NoSolutionExists.new(feedback, solution_constraints[idx],
                                                     disabled_non_existent_packages,
                                                     disabled_most_constrained_packages)
            end
          end
        end
      end
    end
def find_solution(solution_constraints, valid_packages = nil)
      # this is a performance optimization so that packages that are
      # completely unreachable by the solution constraints don't get
      # added to the CSP
      packages_to_include_in_solve = trim_unreachable_packages(dep_graph, solution_constraints)

      begin
        # first, try to solve the whole set of constraints
        solve(dep_graph.clone, solution_constraints, valid_packages, packages_to_include_in_solve)
      rescue Exceptions::NoSolutionFound, Exceptions::TimeBoundExceededNoSolution
        # since we're here, solving the whole system failed, so add
        # the solution_constraints one-by-one and try to solve in
        # order to find the constraint that breaks the system in order
        # to give helpful debugging info
        #
        # TODO [cw,2010/11/28]: for an efficiency gain, instead of
        # continually re-building the problem and looking for a
        # solution, turn solution_constraints into a Generator and
        # iteratively add and solve in order to re-use
        # propagations. This will require separating setting up the
        # constraints from searching for the solution.
        Timeout::timeout(@time_bound, Exceptions::TimeBoundExceededNoSolution) do
          solution_constraints.each_index do |idx|
            workspace = dep_graph.clone
            begin
              solve(workspace, solution_constraints[0..idx], valid_packages, packages_to_include_in_solve)
            rescue Exceptions::NoSolutionFound => nsf
              disabled_packages =
                packages_to_include_in_solve.inject([]) do |acc, elt|
                pkg = workspace.package(elt.name)
                acc << pkg if nsf.unsatisfiable_problem.is_package_disabled?(pkg.gecode_package_id)
                acc
              end
              # disambiguate between packages disabled becuase they
              # don't exist and those that have otherwise problematic
              # constraints
              disabled_non_existent_packages = []
              disabled_most_constrained_packages = []
              disabled_packages.each do |disabled_pkg|
                disabled_collection =
                  if disabled_pkg.valid? || (valid_packages && valid_packages.include?(disabled_pkg))
                    disabled_most_constrained_packages
                  else
                    disabled_non_existent_packages
                  end
                disabled_collection << disabled_pkg
              end

              # Pick the first non-existent or most-constrained package
              # that was required or the package whose constraints had
              # to be disabled in order to find a solution and generate
              # feedback for it. We only report feedback for one
              # package, because it is in fact actionable and dispalying
              # feedback for every disabled package would probably be
              # too long. The full set of disabled packages is
              # accessible in the NoSolutionExists exception.
              disabled_package_to_report_on = disabled_non_existent_packages.first ||
                disabled_most_constrained_packages.first
              feedback = error_reporter.give_feedback(dep_graph, solution_constraints, idx,
                                                      disabled_package_to_report_on)

              raise Exceptions::NoSolutionExists.new(feedback, solution_constraints[idx],
                                                     disabled_non_existent_packages,
                                                     disabled_most_constrained_packages)
            end
          end
        end
      end
    end
Based on solution_constraints, this method tries to find an
 assignment of PackageVersions that is compatible with the
 DependencyGraph. If one cannot be found, the constraints are
 added one at a time until the first unsatisfiable constraint is
 detected. Once the unsatisfiable solution constraint is
 identified, required non-existent packages and the most
 constrained packages are identified and thrown in a
 NoSolutionExists exception.

 If a solution constraint refers to a package that doesn't exist
 or the constraint matches no versions, it is considered
 invalid. All invalid solution constraints are collected and
 raised in an InvalidSolutionConstraints exception. If
 valid_packages is non-nil, it is considered the authoritative
 list of extant Packages; otherwise, Package#valid? is used. This
 is useful if the dependency graph represents an already filtered
 set of packages such that a Package actually exists in your
 domain but is added to the dependency graph with no versions, in
 which case Package#valid? would return false even though we
 don't want to report that the package is non-existent.