From d77ab5d74f82e26c8c2b1dadd3b1033472e8f45a Mon Sep 17 00:00:00 2001 From: Katherine Mantel Date: Tue, 3 Oct 2023 06:02:40 +0000 Subject: [PATCH] mg big gsc/conflict/etc --- src/graph_scheduler/condition.py | 349 +++++++---------------------- tests/scheduling/test_condition.py | 56 ++++- 2 files changed, 125 insertions(+), 280 deletions(-) diff --git a/src/graph_scheduler/condition.py b/src/graph_scheduler/condition.py index c2659204..02fb62e8 100644 --- a/src/graph_scheduler/condition.py +++ b/src/graph_scheduler/condition.py @@ -421,7 +421,8 @@ class Action(enum.Enum): MERGE = 'MERGE' # rename to UNION REPLACE = 'REPLACE' DISCARD = 'DISCARD' - # add DIFFERENCE + DIFFERENCE = 'DIFFERENCE' + SYMMETRIC_DIFFERENCE = 'SYMMETRIC_DIFFERENCE' @staticmethod def is_compatible(sender_action, receiver_action): @@ -2411,9 +2412,9 @@ def __init__( subject_senders: typing.Union[Action, str, dict] = Action.KEEP, subject_receivers: typing.Union[Action, str, dict] = Action.KEEP, reconnect_non_subject_receivers: bool = True, - favor_owner_in_conflicts: bool = True, remove_new_self_referential_edges: bool = True, debug: bool = False, + ignore_conflicts: bool = False, **kwargs, ): subject_senders = self._handle_subject_arg( @@ -2430,9 +2431,9 @@ def __init__( subject_senders=subject_senders, subject_receivers=subject_receivers, reconnect_non_subject_receivers=reconnect_non_subject_receivers, - favor_owner_in_conflicts=favor_owner_in_conflicts, remove_new_self_referential_edges=remove_new_self_referential_edges, debug=debug, + ignore_conflicts=ignore_conflicts, **kwargs ) @@ -2461,89 +2462,76 @@ def _get_subject_action( except KeyError as e: raise ConditionError('TODO text wrong action dict') from e - def _manipulate_graph( - self, - graph: typing.Dict[typing.Hashable, typing.Set] - ) -> typing.Dict[typing.Hashable, typing.Set]: - - def get_conflict_application_string(gsc_arg, applied_to, applied_from): - return '\t{0} {1} applied on {2} from {3}'.format( - str(gsc_arg).ljust(self._actions_names_max_length()), - f'({getattr(self, gsc_arg)})'.ljust(Action._member_names_max_length() + 2), - applied_to, - ','.join(applied_from), - ) - - # def get_conflict_comparison_string(sender, receiver, sender_is_present): - # if sender_is_present: - # make_str = 'makes' - - # return f'\t\t' - # def get_conflict_detail_string( - # sender_action, receiver_action, sender, receiver - # ): - # return ( - # f'{sender_action} makes {receiver} a sender of {sender} but' - # f' {receiver_action} does not make {sender} a receiver of {receiver}' - # ) - + def _get_conflict_application_string(self, gsc_arg, applied_to, applied_from): + return '\t{0} {1} applied on {2} from {3}'.format( + str(gsc_arg).ljust(self._actions_names_max_length()), + f'({getattr(self, gsc_arg)})'.ljust(Action._member_names_max_length() + 2), + applied_to, + ','.join(applied_from), + ) - def check_conflict(sender, receiver): - if receiver is self.owner: - sender_action = 'owner_senders' - sender_sources = self.nodes - else: - sender_action = 'subject_senders' - sender_sources = [self.owner] + def _get_edge_conflicts(self, sender, receiver, new_sender_nodes, new_receiver_nodes): + if receiver is self.owner: + sender_action = 'owner_senders' + sender_sources = self.nodes + else: + sender_action = 'subject_senders' + sender_sources = [self.owner] - if sender is self.owner: - receiver_action = 'owner_receivers' - receiver_sources = self.nodes - else: - receiver_action = 'subject_receivers' - receiver_sources = [self.owner] + if sender is self.owner: + receiver_action = 'owner_receivers' + receiver_sources = self.nodes + else: + receiver_action = 'subject_receivers' + receiver_sources = [self.owner] - conflict_strings = [] + conflict_strings = [] - if ( - receiver in new_sender_nodes - and ( - sender in new_sender_nodes[receiver] - and (sender in new_receiver_nodes and receiver not in new_receiver_nodes[sender]) - ) - ): - conflict_strings.append( - get_conflict_application_string(sender_action, receiver, sender_sources) - + f' makes {sender} a sender of {receiver}' - + '\n' - + get_conflict_application_string(receiver_action, sender, receiver_sources) - + f' does not make {receiver} a receiver of {sender}' - ) - elif ( - sender in new_receiver_nodes - and ( - receiver in new_receiver_nodes[sender] - and (receiver in new_sender_nodes and sender not in new_sender_nodes[receiver]) + if ( + receiver in new_sender_nodes + and ( + sender in new_sender_nodes[receiver] + and (sender in new_receiver_nodes and receiver not in new_receiver_nodes[sender]) + ) + ): + conflict_strings.append( + '{0} {1}\n{2} {3}'.format( + self._get_conflict_application_string(sender_action, receiver, sender_sources), + f'makes {sender} a sender of {receiver}', + self._get_conflict_application_string(receiver_action, sender, receiver_sources), + f'does not make {receiver} a receiver of {sender}', ) - ): - conflict_strings.append( - get_conflict_application_string(receiver_action, sender, receiver_sources) - + f' makes {receiver} a receiver of {sender}' - + '\n' - + get_conflict_application_string(sender_action, receiver, sender_sources) - + f' does not make {sender} a sender of {receiver}' + ) + elif ( + sender in new_receiver_nodes + and ( + receiver in new_receiver_nodes[sender] + and (receiver in new_sender_nodes and sender not in new_sender_nodes[receiver]) + ) + ): + conflict_strings.append( + '{0} {1}\n{2} {3}'.format( + self._get_conflict_application_string(receiver_action, sender, receiver_sources), + f'makes {receiver} a receiver of {sender}', + self._get_conflict_application_string(sender_action, receiver, sender_sources), + f'does not make {sender} a sender of {receiver}', ) + ) - return conflict_strings + return conflict_strings - nodes_used = {self.owner}.union(self.nodes) + def _manipulate_graph( + self, + graph: typing.Dict[typing.Hashable, typing.Set] + ) -> typing.Dict[typing.Hashable, typing.Set]: graph = super()._manipulate_graph(graph) all_receivers_old = get_receivers(graph) old_owner_sender_nodes = graph[self.owner] old_subject_sender_nodes = {n: graph[n] for n in self.nodes} - new_sender_nodes = {} new_receiver_nodes = {} + conflict_strings = [] + result_graph = clone_graph(graph) logger.debug(f'Apply owner_senders to {self.owner}') new_sender_nodes[self.owner] = self._apply_action_to_edge_sets( @@ -2573,214 +2561,37 @@ def check_conflict(sender, receiver): self._get_subject_action(self.subject_receivers, n), ) - for sender in nodes_used: - if sender is self.owner: - receiver_action = 'owner_receivers' - else: - receiver_action = 'subject_receivers' - - for receiver in nodes_used: - if receiver is self.owner: - sender_action = 'owner_senders' - else: - sender_action = 'subject_senders' - - # if ( - # sender in new_sender_nodes - # and ( - # receiver in new_sender_nodes[sender] - # and (receiver not in new_receiver_nodes or sender not in new_receiver_nodes[receiver]) - # ) - # ): - - - - - - print('npn', new_sender_nodes[self.owner]) - - - # for node in new_receiver_nodes: - # if node in new_receiver_nodes[node]: - # # log discard here - # new_receiver_nodes[node].remove(node) - - print([set.union(*[all_receivers_old[n] for n in self.nodes])]) - print(*[set.union(*[all_receivers_old[n] for n in self.nodes])]) - print('ncn', new_receiver_nodes) - - - - # conflicts_owner_senders = set() # and subject receivers - # conflicts_owner_receivers = set() # and subject senders - # conflicts_other_in_senders = set() - # conflicts_other_in_receivers = set() - - # owner_sender_not_subject_receiver = set() #new_sender_nodes[self.owner].difference(set.union(*new_receiver_nodes.values())) - # subject_receiver_not_owner_sender = set() - - # owner_receiver_not_subject_sender = set() - # subject_sender_not_owner_receiver = set() - # conflicts_owner_senders = new_sender_nodes[self.owner].symmetric_difference(set.union(*new_receiver_nodes.values())) - - # for node in new_sender_nodes: - # for sender in new_sender_nodes[node]: - # if node not in new_receiver_nodes[sender]: - # pair = (sender, node) - # if node == self.owner: - # owner_sender_not_subject_receiver.add(pair) - # elif sender == self.owner: - # subject_sender_not_owner_receiver.add(pair) - # else: - # conflicts_other_in_senders.add(pair) - - # for node in new_receiver_nodes: - # for receiver in new_receiver_nodes[node]: - # if node not in new_sender_nodes[receiver]: - # pair = (node, receiver) - # if node == self.owner: - # subject_receiver_not_owner_sender.add(pair) - # elif receiver == self.owner: - # owner_receiver_not_subject_sender.add(pair) - # else: - # conflicts_other_in_receivers.add(pair) - - - # for receiver in new_sender_nodes[sender]: - # if sender not in new_receiver_nodes[receiver]: - # pair = (sender, receiver) - # if receiver == self.owner: - # conflicts_owner_receivers.add(pair) - # elif receiver in self.nodes: - # conflicts_owner_senders.add(pair) - # else: - # conflicts_other.add(pair) - - # print('c os n sr', owner_sender_not_subject_receiver) - # print('c sr n os', subject_receiver_not_owner_sender) - # print('c or n ss', owner_receiver_not_subject_sender) - # print('c ss n or', subject_sender_not_owner_receiver) - - # print('c o is', conflicts_other_in_senders) - # print('c o ir', conflicts_other_in_receivers) - - result_graph = clone_graph(graph) - conflict_strings = [] for receiver in new_sender_nodes: - for sender in new_sender_nodes[receiver]: - conflict_strings.extend(check_conflict(sender, receiver)) + if not self.ignore_conflicts: + for sender in new_sender_nodes[receiver]: + conflict_strings.extend(self._get_edge_conflicts(sender, receiver, new_sender_nodes, new_receiver_nodes)) result_graph[receiver] = new_sender_nodes[receiver] + print('UHHHH??') # sender has an action that changes its receiver nodes # ([owner/subject]_receiver) for sender in new_receiver_nodes: print('checking sender in ncn', sender) for node in graph: - print('checking node', node) - print('rg [node]', result_graph[node]) # [owner/subject]_receiver action applied to sender includes node - # if node in new_receiver_nodes: if node in new_receiver_nodes[sender]: - conflict_strings.extend(check_conflict(sender, node)) - print('node', node, 'is receiver of', sender, 'adding to resgraph sub', node) + if not self.ignore_conflicts: + conflict_strings.extend(self._get_edge_conflicts(sender, node, new_sender_nodes, new_receiver_nodes)) result_graph[node].add(sender) - # [owner/subject]_receiver action applied to sender includes node + logger.debug(f'node {node} is a receiver of {sender} - adding to result graph entry {node}') + # [owner/subject]_receiver action applied to sender does not include node else: try: result_graph[node].remove(sender) except KeyError: pass else: - print('node', node, 'is NOT receiver of', sender, 'discarding from resgraph sub', node) - # if node not in new_sender_nodes and sender in new_receiver_nodes[sender]: - # raise_conflict(sender, node) - if len(conflict_strings) > 0: + logger.debug(f'node {node} is NOT a receiver of {sender} - discarding from result graph entry {node}') + + if not self.ignore_conflicts and len(conflict_strings) > 0: err_prefix = 'Conflict between actions:\n' raise ConditionError(err_prefix + '\n\t-----\n'.join(conflict_strings)) - print('rg', result_graph) - - def has_node_as_receiver(node): - return {r for r in result_graph if r in result_graph[node]} - - def conflict_string(node, new_senders: bool): - if new_senders: - includes_str = 'senders' - excludes_str = 'receivers' - else: - includes_str = 'receivers' - excludes_str = 'senders' - return f'new {includes_str} of {node} not that are not in new {excludes_str}' - - new_nodes_that_have_as_receiver = {node: has_node_as_receiver(node) for node in nodes_used} - - logger.debug('nnthar') - logger.debug(new_nodes_that_have_as_receiver) - - # kept by owner_senders, lost by ?subject_receivers? - # sonnr = new_sender_nodes[self.owner].difference(new_nodes_that_have_as_receiver[self.owner]) - # print(conflict_string(self.owner, True), sonnr) - - # # kept by ?subject_receivers?, lost by owner_senders - # nrnso = new_nodes_that_have_as_receiver[self.owner].difference(new_sender_nodes[self.owner]) - # print('new receivers of owner not that are not in new senders: ', nrnso) - - def check_conflicts(node, node_set, comparison_set): - conflicts = node_set.difference(comparison_set) - return conflicts - if len(conflicts) > 0: - s = conflict_string(node, True), conflicts - raise ConditionError(s) - - - for node in nodes_used: - conflicts_from_node_senders = check_conflicts(node, new_sender_nodes[node], new_nodes_that_have_as_receiver[node]) - conflicts_from_node_receivers = check_conflicts(node, new_nodes_that_have_as_receiver[node], new_sender_nodes[node]) - - print('cns', conflicts_from_node_senders) - - for c in conflicts_from_node_senders: - print('c', c) - if node == self.owner: - conflicting_action_send = self.owner_senders, 'owner_senders', 'owner' - conflicting_action_receive = self._get_subject_action(self.subject_receivers, node), 'subject_receivers', node - else: - conflicting_action_send = self._get_subject_action(self.subject_senders, node), 'subject_senders', node - conflicting_action_receive = self.owner_receivers, 'owner_receivers', 'owner' - - logger.error(f'Conflict between actions {conflicting_action_send} and {conflicting_action_receive}: {c} in new senders of {node} but {node} not a receiver of {c}') - - - - # if len(conflicts_from_node_receivers) > 0: - # if node == self.owner: - # conflicting_action = self.owner_receivers - # else: - # conflicting_action = self._get_subject_action(self.subject_receivers, node) - - # if node == self.owner: - # owner_action = self.owner_senders - # subject_action = self._get_subject_action(self.subject_receivers, node) - - # conflicting_action = self.owner_receivers - # else: - # conflicting_action = self._get_subject_action(self.subject_senders, node) - # check_conflicts(node, new_sender_nodes[node], new_nodes_that_have_as_receiver[node]) - - # conflicting_action = self._get_subject_action(self.subject_receivers, node) - # check_conflicts(node, new_nodes_that_have_as_receiver[node], new_sender_nodes[node]) - - - - # new_send_not_receive = new_sender_nodes[node].difference(new_nodes_that_have_as_receiver[node]) - # new_receive_not_send = new_nodes_that_have_as_receiver[node].difference(new_sender_nodes[node]) - # if len(new_send_not_receive) > 0: - # if len(new_receive_not_send) > 0: - # s = conflict_string(node, False), new_receive_not_send - # raise ConditionError(s) - - - if self.reconnect_non_subject_receivers: for sender in old_owner_sender_nodes: # ONLY add new edges if self.owner is no longer a receiver of sender @@ -2795,6 +2606,7 @@ def check_conflicts(node, node_set, comparison_set): if node not in graph[node]: result_graph[node].discard(node) + logger.debug(f'graph after {self} manipulate_graph:', result_graph) return result_graph def _apply_action_to_edge_sets( @@ -2813,6 +2625,10 @@ def _apply_action_to_edge_sets( res = set(subject_neighbors) elif action == Action.DISCARD: res = set() + elif action == Action.DIFFERENCE: + res = source_neighbors.difference(subject_neighbors) + elif action == Action.SYMMETRIC_DIFFERENCE: + res = source_neighbors.symmetric_difference(subject_neighbors) else: raise ConditionError(f'Unknown Action {action}') @@ -2848,19 +2664,6 @@ def _handle_subject_arg(self, subject, nodes, name): return subject - @property - def behavior(self): - return { - 'owner': { - 'senders': self.owner_senders, - 'receivers': self.owner_receivers, - }, - 'subject': { - 'senders': self.subject_senders, - 'receivers': self.subject_receivers, - } - } - @classmethod def _actions_names_max_length(cls): sig = inspect.signature(cls) diff --git a/tests/scheduling/test_condition.py b/tests/scheduling/test_condition.py index f1f3e5d2..ca4e0382 100644 --- a/tests/scheduling/test_condition.py +++ b/tests/scheduling/test_condition.py @@ -16,12 +16,12 @@ 'four_node_diamond': {'A': set(), 'B': {'A'}, 'C': {'A'}, 'D': {'B', 'C'}}, 'five_node_hub': {'A': set(), 'B': set(), 'C': {'A', 'B'}, 'D': {'C'}, 'E': {'C'}}, 'six_node_multilinear': pytest.helpers.create_graph_from_pathways(['A', 'B', 'C'], ['D', 'E'], ['F']), - 'eight_node_multi': { + 'nine_node_multi': { 'A': set(), 'B': set(), 'C': {'A', 'B'}, - 'D': {'C'}, - 'E': {'C'}, + 'D': {'A', 'C'}, + 'E': {'C', 'H'}, 'F': {'D', 'E'}, 'G': set(), 'H': {'G'}, @@ -48,6 +48,10 @@ def verify_action(action, test_neighbors, source_neighbors, subject_neighbors): assert test_neighbors == subject_neighbors elif action is gs.Action.DISCARD: assert test_neighbors == set() + elif action == gs.Action.DIFFERENCE: + assert test_neighbors == source_neighbors.difference(subject_neighbors) + elif action == gs.Action.SYMMETRIC_DIFFERENCE: + assert test_neighbors == source_neighbors.symmetric_difference(subject_neighbors) else: assert False, f"Unrecognized action {action}" @@ -1732,10 +1736,47 @@ class TestBaseManipulateGraph: @pytest.mark.parametrize( 'graph_name, owner, subject, owner_senders, owner_receivers, subject_senders, subject_receivers, expected_graph', [ - ('five_node_hub', 'A', 'B', gs.Action.KEEP, gs.Action.KEEP, gs.Action.KEEP, gs.Action.KEEP, {}), - # ('four_node_diamond', ), - # ('six_node_multilinear', ), - ] + ( + 'nine_node_multi', + 'E', + 'D', + gs.Action.DIFFERENCE, + gs.Action.REPLACE, + gs.Action.INTERSECT, + gs.Action.SYMMETRIC_DIFFERENCE, + { + 'A': set(), + 'B': set(), + 'C': {'A', 'B'}, + 'D': {'C'}, + 'E': {'H'}, + 'F': {'E'}, + 'G': set(), + 'H': {'G'}, + 'I': {'H'} + }, + ), + ( + 'nine_node_multi', + 'E', + 'D', + gs.Action.INTERSECT, + gs.Action.DISCARD, + gs.Action.MERGE, + gs.Action.INTERSECT, + { + 'A': set(), + 'B': set(), + 'C': {'A', 'B'}, + 'D': {'A', 'C', 'H'}, + 'E': {'C'}, + 'F': {'D'}, + 'G': set(), + 'H': {'G'}, + 'I': {'H'}, + }, + ), + ], ) def test_single_action_single_subject( self, @@ -1771,6 +1812,7 @@ def test_single_action_single_subject( verify_action(subject_senders, new_graph[subject], graph[subject], graph[owner]) verify_action(subject_receivers, new_receivers[subject], old_receivers[subject], old_receivers[owner]) + assert new_graph == expected_graph # @pytest.mark.skip # @pytest.mark.parametrize('subject_receivers', gs.Action)