Skip to content

Commit 3fad86e

Browse files
committed
Optimistic Backjumping
1 parent 24b6fa4 commit 3fad86e

File tree

1 file changed

+45
-8
lines changed

1 file changed

+45
-8
lines changed

src/resolvelib/resolvers/resolution.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ def __init__(
7777
self._r = reporter
7878
self._states: list[State[RT, CT, KT]] = []
7979

80+
# Optimistic backjumping variables
81+
self._optimistic_backjumping = True
82+
self._save_states: list[State[RT, CT, KT]] | None = None
83+
8084
@property
8185
def state(self) -> State[RT, CT, KT]:
8286
try:
@@ -324,11 +328,24 @@ def _backjump(self, causes: list[RequirementInformation[RT, CT]]) -> bool:
324328
except (IndexError, KeyError):
325329
raise ResolutionImpossible(causes) from None
326330

327-
# Only backjump if the current broken state is
328-
# an incompatible dependency
329-
if name not in incompatible_deps:
331+
# If optimistic backjumping has switched off only backjump
332+
# when the current candidate is in the incompatible dependencies
333+
safe_backjump = name in incompatible_deps
334+
if not self._optimistic_backjumping and not safe_backjump:
330335
break
331336

337+
# On the first time a non-regular backjump is done the state
338+
# is saved so we can restore it later if the resolution fails
339+
if not safe_backjump and self._save_states is None:
340+
self._save_states = [
341+
State(
342+
mapping=s.mapping.copy(),
343+
criteria=s.criteria.copy(),
344+
backtrack_causes=s.backtrack_causes[:],
345+
)
346+
for s in self._states
347+
]
348+
332349
# If the current dependencies and the incompatible dependencies
333350
# are overlapping then we have found a cause of the incompatibility
334351
current_dependencies = {
@@ -448,12 +465,32 @@ def resolve(self, requirements: Iterable[RT], max_rounds: int) -> State[RT, CT,
448465
# Backjump if pinning fails. The backjump process puts us in
449466
# an unpinned state, so we can work on it in the next round.
450467
self._r.resolving_conflicts(causes=causes)
451-
success = self._backjump(causes)
452-
self.state.backtrack_causes[:] = causes
453468

454-
# Dead ends everywhere. Give up.
455-
if not success:
456-
raise ResolutionImpossible(self.state.backtrack_causes)
469+
# If optimistic backjumping fails restore previous state
470+
try:
471+
success = self._backjump(causes)
472+
except ResolutionImpossible:
473+
if self._optimistic_backjumping and self._save_states:
474+
failed_optimistic_backjumping = True
475+
else:
476+
raise
477+
else:
478+
failed_optimistic_backjumping = bool(
479+
not success
480+
and self._optimistic_backjumping
481+
and self._save_states
482+
)
483+
484+
if failed_optimistic_backjumping and self._save_states:
485+
self._optimistic_backjumping = False
486+
self._states = self._save_states
487+
self._save_states = None
488+
else:
489+
self.state.backtrack_causes[:] = causes
490+
491+
# Dead ends everywhere. Give up.
492+
if not success:
493+
raise ResolutionImpossible(self.state.backtrack_causes)
457494
else:
458495
# discard as information sources any invalidated names
459496
# (unsatisfied names that were previously satisfied)

0 commit comments

Comments
 (0)