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)
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)