Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert backjumping #142

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 11 additions & 37 deletions src/resolvelib/resolvers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import collections
import itertools
import operator
from typing import (
TYPE_CHECKING,
Expand Down Expand Up @@ -269,8 +268,8 @@ def _attempt_to_pin_criterion(self, name: KT) -> list[Criterion[RT, CT]]:
# end, signal for backtracking.
return causes

def _backjump(self, causes: list[RequirementInformation[RT, CT]]) -> bool:
"""Perform backjumping.
def _backtrack(self) -> bool:
"""Perform backtracking.

When we enter here, the stack is like this::

Expand All @@ -286,47 +285,22 @@ def _backjump(self, causes: list[RequirementInformation[RT, CT]]) -> bool:

Each iteration of the loop will:

1. Identify Z. The incompatibility is not always caused by the latest
state. For example, given three requirements A, B and C, with
dependencies A1, B1 and C1, where A1 and B1 are incompatible: the
last state might be related to C, so we want to discard the
previous state.
2. Discard Z.
3. Discard Y but remember its incompatibility information gathered
1. Discard Z.
2. Discard Y but remember its incompatibility information gathered
previously, and the failure we're dealing with right now.
4. Push a new state Y' based on X, and apply the incompatibility
3. Push a new state Y' based on X, and apply the incompatibility
information from Y to Y'.
5a. If this causes Y' to conflict, we need to backtrack again. Make Y'
4a. If this causes Y' to conflict, we need to backtrack again. Make Y'
the new Z and go back to step 2.
5b. If the incompatibilities apply cleanly, end backtracking.
4b. If the incompatibilities apply cleanly, end backtracking.
"""
incompatible_reqs: Iterable[CT | RT] = itertools.chain(
(c.parent for c in causes if c.parent is not None),
(c.requirement for c in causes),
)
incompatible_deps = {self._p.identify(r) for r in incompatible_reqs}
while len(self._states) >= 3:
# Remove the state that triggered backtracking.
del self._states[-1]

# Ensure to backtrack to a state that caused the incompatibility
incompatible_state = False
broken_state = self.state
while not incompatible_state:
# Retrieve the last candidate pin and known incompatibilities.
try:
broken_state = self._states.pop()
name, candidate = broken_state.mapping.popitem()
except (IndexError, KeyError):
raise ResolutionImpossible(causes) from None
current_dependencies = {
self._p.identify(d)
for d in self._p.get_dependencies(candidate)
}
incompatible_state = not current_dependencies.isdisjoint(
incompatible_deps
)

# Retrieve the last candidate pin and known incompatibilities.
broken_state = self._states.pop()
name, candidate = broken_state.mapping.popitem()
incompatibilities_from_broken = [
(k, list(v.incompatibilities))
for k, v in broken_state.criteria.items()
Expand Down Expand Up @@ -436,7 +410,7 @@ def resolve(
# Backjump if pinning fails. The backjump process puts us in
# an unpinned state, so we can work on it in the next round.
self._r.resolving_conflicts(causes=causes)
success = self._backjump(causes)
success = self._backtrack()
self.state.backtrack_causes[:] = causes

# Dead ends everywhere. Give up.
Expand Down
Loading