def __init__(self, k: int = None): super().__init__(k) self._coloring_tip_gid = None # The gid of the tip of the coloring chain self._coloring_chain = set() # A set of the coloring chain self._k_chain = self.KChain(set(), float('inf')) # The "main" k-chain # The various data structures to hold the coloring and ordering of the DAG self._blue_past_order = ChainMap() self._red_past_order = ChainMap() # The antipast is in essence the diffpast between the virtual block and its coloring parent, # who is simply the coloring tip of the entire DAG. self._blue_antipast_order = dict() self._red_antipast_order = dict() self._uncolored_unordered_antipast = LazySet() # Some unifying data structures to make coloring and ordering easier self._past_order = ChainMap(self._blue_past_order, self._red_past_order) self._antipast_order = ChainMap(self._blue_antipast_order, self._red_antipast_order) self._antipast = ChainMap(self._antipast_order, self._uncolored_unordered_antipast).keys() self._coloring_order = ChainMap(self._blue_past_order, self._blue_antipast_order) # The coloring is in essence the virtual block's coloring of the entire DAG self._coloring = self._coloring_order.keys() # The mapping is in essence the virtual block's ordering of the entire DAG self._mapping = ChainMap(self._past_order, self._antipast_order)
def _get_antipast( self, global_id: Block.GlobalID) -> AbstractSet[Block.GlobalID]: """ :return: the antipast of the block with the given global id. """ if global_id is None: return self._mapping.keys() if global_id == self._coloring_tip_gid: return self._antipast positive_sets, negative_sets = [], [] for cur_chain_gid, is_main_coloring_chain, is_intersection in \ self._local_tip_to_global_tip_generator(global_id): if not is_main_coloring_chain or is_intersection: append_to = negative_sets else: append_to = positive_sets append_to.append(self._G.node[cur_chain_gid][ self._BLUE_DIFF_PAST_ORDER_KEY].keys()) append_to.append(self._G.node[cur_chain_gid][ self._RED_DIFF_PAST_ORDER_KEY].keys()) antipast = LazySet(base_set=self._antipast, positive_sets=positive_sets) for negative_set in negative_sets: antipast.lazy_difference_update(negative_set) # See the remark on antipast usage in _update_past_coloring_according_to # antipast.flatten() return antipast
def _get_past(self, global_id: Block.GlobalID) -> AbstractSet[Block.GlobalID]: """ :return: the past of the block with the given global id. """ if global_id is None: return set() positive_chain, negative_chain = ChainMap(), ChainMap() for cur_chain_gid, is_main_coloring_chain, is_intersection in \ self._local_tip_to_global_tip_generator(global_id): if is_intersection: continue if not is_main_coloring_chain: append_to = positive_chain.maps else: append_to = negative_chain.maps append_to.append( self._G.node[cur_chain_gid][self._BLUE_DIFF_PAST_ORDER_KEY]) append_to.append( self._G.node[cur_chain_gid][self._RED_DIFF_PAST_ORDER_KEY]) return LazySet(base_set=self._past_order.keys(), negative_sets=[negative_chain.keys()], positive_sets=[positive_chain.keys()])
def _get_coloring_chain( self, global_id: Block.GlobalID, length: float = float("inf")) -> LazySet: """ :param global_id: the global id of the last block of the chain. Block must be in the DAG. :param length: optional, a cutoff for the number of blocks in the chain. :return: a LazySet of the global ids of blocks in the coloring chain of the given global id. The coloring chain is simply a chain ending with a block, such that for each block, the block before him in the chain is his "coloring parent". """ infinite_length = length == float("inf") main_chain_intersection_gid = None base_set = set() positive_fork = set() negative_fork = set() count = 0 for cur_gid in self._coloring_chain_generator(global_id): if count >= length + 1: break # Note that for complete coloring chains it is enough to find the first coloring ancestor block # that is in the main coloring chain - continuing past this point is pointless if cur_gid in self._coloring_chain and infinite_length: main_chain_intersection_gid = cur_gid break positive_fork.add(cur_gid) count += 1 if infinite_length and (main_chain_intersection_gid is not None): base_set = self._coloring_chain for cur_gid in self._coloring_chain_generator( self._coloring_tip_gid): if cur_gid == main_chain_intersection_gid: break negative_fork.add(cur_gid) count += 1 return LazySet(base_set, [negative_fork], [positive_fork])
def interleaving_test_iterator(base_set, negative_sets, positive_sets, more_negative_sets, more_positive_sets, intersection_sets, symmetric_difference_sets, update, numeric_operations): """ Iterates on the test by alternatingly adding positive and negative sets to the LazySet and yielding the according LazySet and regular set. :param update: whether to use methods that update the LazySet in-place or not. :param numeric_operations: whether to use numeric-operation-style methods (e.g. |, -, etc') when creating the LazySet or not. """ lazy_set = LazySet(base_set=base_set, negative_sets=negative_sets, positive_sets=positive_sets) regular_set = base_set.difference(*negative_sets).union(*positive_sets) yield lazy_set, regular_set # "base" test yield lazy_set.copy(), regular_set yield lazy_set.copy_to_set(), regular_set yield lazy_set.copy().flatten(), regular_set yield lazy_set.copy().flatten(True), regular_set # re-run the test after each successive modification to the sets index = 0 while index <= max(len(more_positive_sets), len(more_negative_sets)): if index < len(more_negative_sets): regular_set -= more_negative_sets[index] if update: if numeric_operations: lazy_set -= more_negative_sets[index] else: lazy_set.difference_update(more_negative_sets[index]) else: if numeric_operations: lazy_set = lazy_set - more_negative_sets[index] else: lazy_set = lazy_set.difference( more_negative_sets[index]) yield lazy_set, regular_set yield lazy_set.copy(), regular_set yield lazy_set.copy_to_set(), regular_set yield lazy_set.copy().flatten(), regular_set yield lazy_set.copy().flatten(True), regular_set if index < len(more_positive_sets): regular_set |= more_positive_sets[index] if update: if numeric_operations: lazy_set |= more_positive_sets[index] else: lazy_set.update(more_positive_sets[index]) else: if numeric_operations: lazy_set = lazy_set | more_positive_sets[index] else: lazy_set = lazy_set.union(more_positive_sets[index]) yield lazy_set, regular_set yield lazy_set.copy(), regular_set yield lazy_set.copy_to_set(), regular_set yield lazy_set.copy().flatten(), regular_set yield lazy_set.copy().flatten(True), regular_set if index < len(intersection_sets): regular_set &= intersection_sets[index] if update: if numeric_operations: lazy_set &= intersection_sets[index] else: lazy_set.intersection_update(intersection_sets[index]) else: if numeric_operations: lazy_set = lazy_set & intersection_sets[index] else: lazy_set = lazy_set.intersection( intersection_sets[index]) yield lazy_set, regular_set yield lazy_set.copy(), regular_set yield lazy_set.copy_to_set(), regular_set yield lazy_set.copy().flatten(), regular_set yield lazy_set.copy().flatten(True), regular_set if index < len(symmetric_difference_sets): regular_set ^= symmetric_difference_sets[index] if update: if numeric_operations: lazy_set ^= symmetric_difference_sets[index] else: lazy_set.symmetric_difference_update( symmetric_difference_sets[index]) else: if numeric_operations: lazy_set = lazy_set ^ symmetric_difference_sets[index] else: lazy_set = lazy_set.symmetric_difference( symmetric_difference_sets[index]) yield lazy_set, regular_set yield lazy_set.copy(), regular_set yield lazy_set.copy_to_set(), regular_set yield lazy_set.copy().flatten(), regular_set yield lazy_set.copy().flatten(True), regular_set index += 1 # clear the sets and run the tests for the final time lazy_set.clear() regular_set.clear() yield lazy_set, regular_set yield lazy_set.copy(), regular_set yield lazy_set.copy_to_set(), regular_set yield lazy_set.copy().flatten(), regular_set yield lazy_set.copy().flatten(True), regular_set
def test_single_element_methods(self): """ Tests the LazySet by adding and then removing single elements to it. """ lazy_set = LazySet() regular_set = set() TestLazySet.basic_test(lazy_set, regular_set) elem1 = 1 lazy_set.add(elem1) regular_set.add(elem1) TestLazySet.basic_test(lazy_set, regular_set) elem2 = '1' lazy_set.add(elem2) regular_set.add(elem2) TestLazySet.basic_test(lazy_set, regular_set) elem3 = None lazy_set.add(elem3) regular_set.add(elem3) TestLazySet.basic_test(lazy_set, regular_set) elem4 = float('inf') lazy_set.add(elem4) regular_set.add(elem4) TestLazySet.basic_test(lazy_set, regular_set) lazy_set.remove(elem2) regular_set.remove(elem2) TestLazySet.basic_test(lazy_set, regular_set) with pytest.raises(KeyError) as lazy_excinfo: lazy_set.remove(elem2) with pytest.raises(KeyError) as regular_excinfo: regular_set.remove(elem2) assert lazy_excinfo.type == regular_excinfo.type assert str(lazy_excinfo.value) == str(regular_excinfo.value) TestLazySet.basic_test(lazy_set, regular_set) lazy_set.discard(elem2) regular_set.discard(elem2) TestLazySet.basic_test(lazy_set, regular_set) lazy_set.discard(elem1) regular_set.discard(elem1) TestLazySet.basic_test(lazy_set, regular_set) lazy_set.add(elem2) regular_set.add(elem2) TestLazySet.basic_test(lazy_set, regular_set) lazy_set.discard(elem2) regular_set.discard(elem2) TestLazySet.basic_test(lazy_set, regular_set) pop_elem = lazy_set.pop() regular_set.remove(pop_elem) TestLazySet.basic_test(lazy_set, regular_set) pop_elem = lazy_set.pop() regular_set.remove(pop_elem) TestLazySet.basic_test(lazy_set, regular_set) with pytest.raises(KeyError) as lazy_excinfo: lazy_set.pop() with pytest.raises(KeyError) as regular_excinfo: regular_set.pop() assert lazy_excinfo.type == regular_excinfo.type