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

iterative branch and bound #1175

Merged
merged 2 commits into from
Dec 19, 2023
Merged
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
59 changes: 18 additions & 41 deletions dedupe/branch_and_bound.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import functools
import warnings
from typing import Any, Iterable, Mapping, Sequence, Tuple

from ._typing import Cover
Expand All @@ -27,13 +26,11 @@
def _uncovered_by(
coverage: Mapping[Any, frozenset[int]], covered: frozenset[int]
) -> dict[Any, frozenset[int]]:
remaining = {}
for predicate, uncovered in coverage.items():
still_uncovered = uncovered - covered
if still_uncovered:
remaining[predicate] = still_uncovered

return remaining
return {

Check warning on line 29 in dedupe/branch_and_bound.py

View check run for this annotation

Codecov / codecov/patch

dedupe/branch_and_bound.py#L29

Added line #L29 was not covered by tests
pred: still_uncovered
for pred, uncovered in coverage.items()
if (still_uncovered := uncovered - covered)
}


def _order_by(
Expand All @@ -46,41 +43,22 @@
return sum(p.cover_count for p in partial)


def _suppress_recursion_error(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except RecursionError:
warnings.warn("Recursion limit eached while searching for predicates")

return wrapper


def search(candidates, target: int, max_calls: int) -> Partial:
calls = max_calls

cheapest_score = float("inf")
cheapest: Partial = ()

original_cover = candidates.copy()

def search(original_cover: Cover, target: int, calls: int) -> Partial:

Check warning on line 46 in dedupe/branch_and_bound.py

View check run for this annotation

Codecov / codecov/patch

dedupe/branch_and_bound.py#L46

Added line #L46 was not covered by tests
def _covered(partial: Partial) -> int:
return (
len(frozenset.union(*(original_cover[p] for p in partial)))
if partial
else 0
)

@_suppress_recursion_error
def walk(candidates: Cover, partial: Partial = ()) -> None:
nonlocal calls
nonlocal cheapest
nonlocal cheapest_score
cheapest_score = float("inf")
cheapest: Partial = ()

Check warning on line 55 in dedupe/branch_and_bound.py

View check run for this annotation

Codecov / codecov/patch

dedupe/branch_and_bound.py#L54-L55

Added lines #L54 - L55 were not covered by tests

if calls <= 0:
return
start: tuple[Cover, Partial] = (original_cover, ())
to_explore = [start]

Check warning on line 58 in dedupe/branch_and_bound.py

View check run for this annotation

Codecov / codecov/patch

dedupe/branch_and_bound.py#L57-L58

Added lines #L57 - L58 were not covered by tests

calls -= 1
while to_explore and calls:
candidates, partial = to_explore.pop()

Check warning on line 61 in dedupe/branch_and_bound.py

View check run for this annotation

Codecov / codecov/patch

dedupe/branch_and_bound.py#L60-L61

Added lines #L60 - L61 were not covered by tests

covered = _covered(partial)
score = _score(partial)
Expand All @@ -97,17 +75,16 @@
order_by = functools.partial(_order_by, candidates)
best = max(candidates, key=order_by)

remaining = _uncovered_by(candidates, candidates[best])
walk(remaining, partial + (best,))
del remaining

reduced = _remove_dominated(candidates, best)
walk(reduced, partial)
del reduced
to_explore.append((reduced, partial))

Check warning on line 79 in dedupe/branch_and_bound.py

View check run for this annotation

Codecov / codecov/patch

dedupe/branch_and_bound.py#L79

Added line #L79 was not covered by tests

remaining = _uncovered_by(candidates, candidates[best])
to_explore.append((remaining, partial + (best,)))

Check warning on line 82 in dedupe/branch_and_bound.py

View check run for this annotation

Codecov / codecov/patch

dedupe/branch_and_bound.py#L81-L82

Added lines #L81 - L82 were not covered by tests

elif score < cheapest_score:
cheapest = partial
cheapest_score = score

walk(candidates)
calls -= 1

Check warning on line 88 in dedupe/branch_and_bound.py

View check run for this annotation

Codecov / codecov/patch

dedupe/branch_and_bound.py#L88

Added line #L88 was not covered by tests

return cheapest
Loading