-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MAINT: speed up the UndeterminedClausePolicy
- Loading branch information
Showing
3 changed files
with
86 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
|
||
from collections import defaultdict | ||
|
||
import six | ||
|
||
from simplesat.utils import DefaultOrderedDict | ||
|
@@ -17,15 +19,20 @@ class UndeterminedClausePolicy(IPolicy): | |
def __init__(self, pool, installed_repository, prefer_installed=True): | ||
self._pool = pool | ||
self.prefer_installed = prefer_installed | ||
self._installed_ids = set( | ||
pool.package_id(package) for package in installed_repository | ||
by_version = six.functools.partial(pkg_id_to_version, self._pool) | ||
self._installed_ids = sorted( | ||
(pool.package_id(package) for package in installed_repository), | ||
key=by_version | ||
) | ||
self._preferred_package_ids = { | ||
self._package_key(package_id): package_id | ||
for package_id in self._installed_ids | ||
} | ||
self._decision_set = set() | ||
self._requirements = set() | ||
self._unsatisfied_clauses = set() | ||
self._id_to_clauses = defaultdict(list) | ||
self._all_ids = set() | ||
|
||
def _package_key(self, package_id): | ||
package = self._pool._id_to_package[package_id] | ||
|
@@ -34,30 +41,47 @@ def _package_key(self, package_id): | |
def add_requirements(self, package_ids): | ||
self._requirements.update(package_ids) | ||
|
||
def _update_cache_from_assignments(self, assignments): | ||
changelog = assignments.consume_changelog() | ||
for key in six.iterkeys(changelog): | ||
for clause in self._id_to_clauses[key]: | ||
if not any(assignments.value(l) for l in clause.lits): | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
johntyree
Author
Contributor
|
||
self._unsatisfied_clauses.add(clause) | ||
else: | ||
self._unsatisfied_clauses.discard(clause) | ||
|
||
def _build_id_to_clauses(self, clauses): | ||
table = defaultdict(list) | ||
This comment has been minimized.
Sorry, something went wrong.
cournape
Contributor
|
||
for c in clauses: | ||
for l in c.lits: | ||
table[abs(l)].append(c) | ||
self._all_ids = set(six.iterkeys(table)) | ||
return dict(table) | ||
|
||
def get_next_package_id(self, assignments, clauses): | ||
"""Get the next unassigned package. | ||
""" | ||
if assignments.new_keys: | ||
self._id_to_clauses = self._build_id_to_clauses(clauses) | ||
self._refresh_decision_set(assignments) | ||
candidate_id = None | ||
best = self._best_candidate | ||
|
||
if self.prefer_installed: | ||
candidate_id = best(self._installed_ids, assignments) | ||
candidate_id = self._best_sorted_candidate( | ||
self._installed_ids, assignments) | ||
|
||
candidate_id = ( | ||
candidate_id or | ||
self._best_candidate(self._requirements, assignments) or | ||
self._best_candidate(self._decision_set, assignments) | ||
) | ||
if candidate_id is None: | ||
candidate_id = best(self._requirements, assignments) | ||
|
||
if candidate_id is None: | ||
candidate_id = best(self._decision_set, assignments, update=True) | ||
|
||
if candidate_id is None: | ||
self._decision_set.clear() | ||
candidate_id = \ | ||
self._handle_empty_decision_set(assignments, clauses) | ||
self._refresh_decision_set(assignments) | ||
candidate_id = best(self._decision_set, assignments) | ||
if candidate_id is None: | ||
candidate_id = self._best_candidate( | ||
self._decision_set, | ||
assignments | ||
) | ||
candidate_id = best(self._all_ids, assignments) | ||
|
||
assert assignments.get(candidate_id) is None, \ | ||
"Trying to assign to a variable which is already assigned." | ||
|
@@ -70,14 +94,19 @@ def get_next_package_id(self, assignments, clauses): | |
return candidate_id | ||
|
||
def _without_assigned(self, package_ids, assignments): | ||
return set( | ||
pkg_id for pkg_id in package_ids | ||
if assignments.get(pkg_id) is None | ||
) | ||
return package_ids.difference(assignments.assigned_ids) | ||
|
||
def _best_sorted_candidate(self, package_ids, assignments): | ||
for p_id in package_ids: | ||
if p_id not in assignments.assigned_ids: | ||
return p_id | ||
|
||
def _best_candidate(self, package_ids, assignments): | ||
def _best_candidate(self, package_ids, assignments, update=False): | ||
by_version = six.functools.partial(pkg_id_to_version, self._pool) | ||
unassigned = self._without_assigned(package_ids, assignments) | ||
if update: | ||
package_ids.clear() | ||
package_ids.update(unassigned) | ||
try: | ||
return max(unassigned, key=by_version) | ||
except ValueError: | ||
|
@@ -86,51 +115,26 @@ def _best_candidate(self, package_ids, assignments): | |
def _group_packages_by_name(self, decision_set): | ||
installed_packages = [] | ||
new_package_map = DefaultOrderedDict(list) | ||
installed_ids = set(self._installed_ids) | ||
|
||
for package_id in sorted(decision_set): | ||
package = self._pool._id_to_package[package_id] | ||
if package_id in self._installed_ids: | ||
if package_id in installed_ids: | ||
installed_packages.append(package) | ||
else: | ||
new_package_map[package.name].append(package) | ||
|
||
return installed_packages, new_package_map | ||
|
||
def _handle_empty_decision_set(self, assignments, clauses): | ||
# TODO inefficient and verbose | ||
|
||
unassigned_ids = set( | ||
literal for literal, status in six.iteritems(assignments) | ||
if status is None | ||
def _refresh_decision_set(self, assignments): | ||
self._update_cache_from_assignments(assignments) | ||
self._decision_set.clear() | ||
self._decision_set.update( | ||
abs(lit) | ||
for clause in self._unsatisfied_clauses | ||
for lit in clause.lits | ||
) | ||
assigned_ids = set(assignments.keys()) - unassigned_ids | ||
|
||
signed_assignments = set() | ||
for variable in assigned_ids: | ||
if assignments[variable]: | ||
signed_assignments.add(variable) | ||
else: | ||
signed_assignments.add(-variable) | ||
|
||
for clause in clauses: | ||
# TODO Need clause.undecided_literals property | ||
if not signed_assignments.isdisjoint(clause.lits): | ||
# Clause is true | ||
continue | ||
|
||
variables = map(abs, clause.lits) | ||
undecided = unassigned_ids.intersection(variables) | ||
self._decision_set.update(lit for lit in undecided) | ||
|
||
if len(self._decision_set) == 0: | ||
# This will happen if the remaining packages are irrelevant for | ||
# the set of rules that we're trying to satisfy. In that case, | ||
# just return one of the undecided IDs. | ||
|
||
# We use min to ensure determinisism | ||
return min(unassigned_ids) | ||
else: | ||
return None | ||
self._decision_set.difference_update(assignments.assigned_ids) | ||
|
||
|
||
LoggedUndeterminedClausePolicy = LoggedPolicy(UndeterminedClausePolicy) |
maybe simpler to say
if any(...): self._unsatisfied_clauses.discard(...)
?