Beispiel #1
0
class DeadlockStack:
    def __init__(self, dl_set=None, fname=None, sample_state=None):
        self.fname = fname
        self.dependencies = Digraph()  # deadlock -> descendants
        if dl_set is None: dl_set = DeadlockSet()
        self.dl_set = dl_set
        self._last_full_index = -1

        self.debug_data = []
        self.debug_fname = "bug.log"

        if fname is not None:
            assert sample_state is not None
            if os.path.exists(fname):
                print("loading deadlocks...")
                try:
                    blocks = deadlocks_from_file(fname, sample_state)
                except:
                    blocks = None

                    def backup_fnames_gen():
                        base_fname = fname + "_backup"
                        yield base_fname
                        i = 0
                        while True:
                            yield base_fname + str(i)

                    backup_fnames = backup_fnames_gen()
                    while True:
                        backup_fname = next(backup_fnames)
                        if not os.path.exists(backup_fname): break
                    os.rename(fname, backup_fname)
                    print("deadlock file corrupted, renamed to '{}'".format(
                        backup_fname))

                if blocks is not None:
                    for dl in chain.from_iterable(blocks):
                        self.debug_data.append(
                            "dummy_deadlocks[{}] = make_dummy_deadlock({})".
                            format(id(dl), dl.full_index))
                        self.dl_set.add(dl)
                        self._last_full_index = dl.full_index
                    print("loaded {} deadlocks".format(self._last_full_index +
                                                       1))

    def add(self, deadlock, stack_index):
        assert stack_index >= 0
        if isinstance(deadlock, SokoState):
            deadlock = deadlock_from_state(deadlock)
        self.debug_data.append(
            "dummy_deadlocks[{}] = dl_stack.add(make_dummy_deadlock(), {})".
            format(
                id(deadlock),
                stack_index,
            ))
        deadlock.stack_index = stack_index
        self.dl_set.add(deadlock)
        self.dependencies.add_node_B(deadlock)
        return deadlock

    # supports removing multiple deadlocks at once
    # discards also deadlocks dependent on it
    def remove(self, deadlocks):
        if isinstance(deadlocks, Deadlock): deadlocks = [deadlocks]
        self.debug_data.append(
            "dl_stack.remove([dummy_deadlocks[i] for i in {}])".format(
                [id(dl) for dl in deadlocks]))
        dependent = self.dependencies.closure_BA(deadlocks)
        for deadlock in dependent:
            self.dl_set.remove(deadlock)
            self.dependencies.remove_node_B(deadlock)
            if deadlock.descendants is not None:
                self.dependencies.remove_node_A(deadlock)

    def make_full(self, deadlock):
        assert deadlock.full_index == None
        deadlock.stack_index = -1
        self.dependencies.remove_node(deadlock)
        self._last_full_index += 1
        deadlock.full_index = self._last_full_index

    def set_descendants(self, deadlock, pushes, descendants):
        self.debug_data.append(
            "dl_stack.set_descendants(dummy_deadlocks[{}], [None]*{}, [dummy_deadlocks[i] for i in {}])"
            .format(
                id(deadlock),
                len(descendants),
                [id(dl) for dl in descendants],
            ))

        try:

            assert deadlock.descendants is None
            assert len(pushes) == len(descendants)
            deadlock.descendants = dict(zip(pushes, descendants))

            # add to dependency graph

            self.dependencies.add_node_A(deadlock)
            for descendant in descendants:
                if descendant.stack_index >= 0:
                    self.dependencies.add_edge(deadlock, descendant)

            # update stack_index where necessary

            to_check = self.dependencies.closure_BA([deadlock])
            ori_stack_index = deadlock.stack_index
            assert all(dl.stack_index == ori_stack_index for dl in to_check)

            # find elements of to_check looking outside
            new_stack_indices = defaultdict(list)
            for dl in to_check:
                new_stack_index = max([
                    desc.stack_index
                    for desc in self.dependencies.neighbors_A(dl)
                    if desc.stack_index != ori_stack_index
                ],
                                      default=-1)
                if new_stack_index >= 0:
                    assert (new_stack_index < ori_stack_index), (
                        new_stack_index, ori_stack_index)
                    new_stack_indices[new_stack_index].append(
                        (dl, new_stack_index))
            dfs_stack = list(
                chain.from_iterable(new_stack_indices[i]
                                    for i in sorted(new_stack_indices.keys())))

            # propagate the right stack_index backwards
            to_check_l = []
            size_of_index = defaultdict(int)
            while dfs_stack:
                dl, i = dfs_stack.pop()
                if dl not in to_check: continue
                to_check_l.append(dl)
                size_of_index[i] += 1
                to_check.remove(dl)
                dl.stack_index = i
                dfs_stack.extend(
                    (dl2, i) for dl2 in self.dependencies.neighbors_B(dl))

            # mark a strongly connected component as a full deadlock
            scc = list(to_check)
            if scc:
                for dl in scc:
                    self.make_full(dl)

                if self.fname is not None:
                    with open(self.fname, 'a') as f:
                        print(file=f)
                        for dl in scc:
                            dl.print_self(file=f)
                    if len(scc) == 1:
                        print("Saved deadlock {}".format(scc[0].full_index))
                    else:
                        print("Saved deadlocks {}-{}".format(
                            scc[0].full_index, scc[-1].full_index))

            # output for checking on path
            to_check_l.reverse()
            return scc, scc + to_check_l, size_of_index

        except Exception:
            if self.debug_fname is not None:
                with open(self.debug_fname, 'w') as f:
                    for l in self.debug_data:
                        print(l, file=f)
                print("error in DeadlockStack occured, debug data stored in " +
                      self.debug_fname)
                self.debug_fname = None

            raise

    def check_correct(self):
        for dl in self.dependencies.nodes_A():
            assert dl.stack_index == max(
                [dl2.stack_index for dl2 in dl.descendants.values()],
                default=-1)
Beispiel #2
0
class DeadlockSet:
    def __init__(self):
        self.box_dl = Digraph()  # box node -> deadlocks

        self._boxes_to_deadlock = defaultdict(list)
        self._box_to_size_to_nodeA = defaultdict(dict)
        self._nbox_to_size_to_nodeA = defaultdict(dict)
        self._last_node = -1

    def _get_node(self, d, box, size):
        node = d[box].get(size, None)
        if node is not None: return node
        self._last_node += 1
        d[box][size] = self._last_node
        self.box_dl.add_node_A(self._last_node)
        return self._last_node

    def add(self, deadlock):
        if isinstance(deadlock, SokoState):
            deadlock = deadlock_from_state(deadlock)
        self.box_dl.add_node_B(deadlock)
        self._boxes_to_deadlock[deadlock.boxes].append(deadlock)
        size = len(deadlock.boxes)
        for box in deadlock.boxes:
            node = self._get_node(self._box_to_size_to_nodeA, box, size)
            self.box_dl.add_edge(node, deadlock)
        for nbox in deadlock.not_boxes:
            node = self._get_node(self._nbox_to_size_to_nodeA, nbox, size)
            self.box_dl.add_edge(node, deadlock)
        return deadlock

    def remove(self, deadlock):
        self._boxes_to_deadlock[deadlock.boxes].remove(deadlock)
        self.box_dl.remove_node_B(deadlock)

    def find(self, new_boxes, new_nboxes, ori_boxes, ori_nboxes, storekeeper):

        size_to_nodes = defaultdict(list)

        def gen_size_to_node():
            for box in new_boxes:
                size_to_node = self._box_to_size_to_nodeA.get(box, None)
                if size_to_node != None: yield size_to_node
            for nbox in new_nboxes:
                size_to_node = self._nbox_to_size_to_nodeA.get(nbox, None)
                if size_to_node != None: yield size_to_node

        for size_to_node in gen_size_to_node():
            for size, nodeA in size_to_node.items():
                size_to_nodes[size].append(nodeA)

        if not size_to_nodes: return None

        boxes_set = set(ori_boxes)
        boxes_set.update(new_boxes)
        boxes_set.difference_update(new_nboxes)
        boxes_sorted = sorted(boxes_set)
        max_size = len(boxes_sorted)
        size_to_nodes_items = sorted(
            filter(lambda item: item[0] <= max_size, size_to_nodes.items()))
        if ori_nboxes is None: nboxes_set = None
        else:
            nboxes_set = set(ori_nboxes)
            nboxes_set.update(new_nboxes)
            nboxes_set.difference_update(new_boxes)

        for size, box_nodes in size_to_nodes_items:
            candidate_sets = [
                self.box_dl.neighbors_A(box_node) for box_node in box_nodes
            ]
            if sum(len(candidates)
                   for candidates in candidate_sets) < size * binom(
                       max_size, size):
                if len(candidate_sets) == 1: candidates = candidate_sets[0]
                else: candidates = set().union(*candidate_sets)
                for deadlock in candidates:
                    if deadlock.check_sets(boxes_set, nboxes_set, storekeeper):
                        yield deadlock
            else:
                for subboxes in combinations(boxes_sorted, size):
                    for deadlock in self._boxes_to_deadlock[subboxes]:
                        if deadlock.sk_component[storekeeper] \
                           and deadlock.nboxes_check_sets(boxes_set, nboxes_set):
                            yield deadlock

    def find_one(self,
                 new_boxes,
                 new_nboxes,
                 ori_boxes,
                 ori_nboxes,
                 storekeeper,
                 condition=None):
        deadlocks = self.find(new_boxes, new_nboxes, ori_boxes, ori_nboxes,
                              storekeeper)
        if condition is not None:
            deadlocks = filter(condition, deadlocks)
        return maybe_next(deadlocks)

    def find_by_state(self, state, ori_state=None):

        sub_boxes = state.sub_boxes
        if state.sub_full: sup_boxes = sub_boxes
        else: sup_boxes = state.sup_boxes

        if ori_state is None:
            ori_sub_boxes = np.zeros_like(state.available)
            ori_sup_boxes = state.available
        else:
            ori_sub_boxes = ori_state.sub_boxes
            if ori_state.sub_full:
                ori_sup_boxes = ori_state.sub_boxes
            else:
                ori_sup_boxes = ori_state.sup_boxes

        ori_boxes = positions_true(sub_boxes)
        ori_nboxes = positions_true(~sup_boxes & state.available)
        new_boxes = positions_true(sub_boxes & ~ori_sub_boxes)
        new_nboxes = positions_true(~sup_boxes & ori_sup_boxes)

        if state.storekeepers is not None:
            storekeeper = state.storekeeper
        else:
            storekeeper = positions_true(state.storekeepers)[0]
        if state.multi_component:
            condition = lambda deadlock: (state.storekeepers <= deadlock.
                                          sk_component).all()
        else:
            condition = None

        return self.find_one(
            new_boxes,
            new_nboxes,
            ori_boxes,
            ori_nboxes,
            storekeeper,
            condition=condition,
        )

    def find_for_box_moves(self, state, box_moves):

        if state.multi_component:
            for box_src, box_dest, sk_dir in box_moves:

                sub_boxes = np.array(state.sub_boxes)
                sup_boxes = np.array(state.sup_boxes)
                sub_boxes[box_src] = False
                sup_boxes[box_src] = False
                sub_boxes[box_dest] = True
                sup_boxes[box_dest] = True
                state2 = SokoState(
                    available=state.available,
                    sub_boxes=sub_boxes,
                    sup_boxes=sup_boxes,
                    storages=state.storages,
                    sub_full=state.sub_full,
                    storekeeper=dir_shift(sk_dir, box_dest),
                    storekeeper_goal=state.storekeeper_goal,
                )
                yield self.find_by_state(state2)

        else:
            ori_boxes = positions_true(state.sub_boxes)
            if state.sub_full: ori_nboxes = None
            else:
                ori_nboxes = positions_true(state.available & ~state.sup_boxes)

            for box_src, box_dest, sk_dir in box_moves:
                storekeeper = dir_shift(sk_dir, box_dest)
                yield self.find_one([box_dest], [box_src], ori_boxes,
                                    ori_nboxes, storekeeper)

    def find_for_actions(self, state, actions, fw_mode=True):
        box_moves = []
        for y, x, d in actions:
            box_src = (y + 1, x + 1)
            box_dest = dir_shift(d, box_src)
            if fw_mode: sk_dir = op_dir(d)
            else: sk_dir = d
            box_moves.append((box_src, box_dest, sk_dir))
        return self.find_for_box_moves(state, box_moves)