From adbf46ba4d312b020509749645f904bcd649e64a Mon Sep 17 00:00:00 2001 From: VIGNET Pierre <pierre.vignet@irisa.fr> Date: Sun, 26 Feb 2017 20:26:36 +0100 Subject: [PATCH] Full method to display trajectories of solutions; Major improvements in condition parsing, and display --- solution_repr.py | 429 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 333 insertions(+), 96 deletions(-) diff --git a/solution_repr.py b/solution_repr.py index 148a974..99a75b6 100644 --- a/solution_repr.py +++ b/solution_repr.py @@ -2,14 +2,26 @@ from __future__ import unicode_literals from __future__ import print_function +# Standard imports import datetime as dt from collections import defaultdict import networkx as nx import itertools as it +import re import matplotlib.pyplot as plt +from logging import DEBUG +# Library imports from cadbiom.models.guard_transitions.translators.chart_xml \ import MakeModelFromXmlFile +from cadbiom.models.biosignal.translators.gt_visitors import compile_cond +from cadbiom.models.guard_transitions.analyser.ana_visitors import TableVisitor +from cadbiom.models.biosignal.sig_expr import * +from cadbiom.models.guard_transitions.analyser.ana_visitors import SigExpIdCollectVisitor + +import cadbiom.commons as cm + +LOGGER = cm.logger() LOG_DIR = "./logs/" BIO_MOLDELS_DIR = "./bio_models/" @@ -43,7 +55,26 @@ Bx Ax % % """ -# 'not' = ' not' '(not' '"not' + +class Reporter(object): + """Error reporter. + + .. note:: Link the lexer to the model allows to avoid error in Reporter + like: "-> dec -> Undeclared event or state" + In practice this is time consuming and useless for what we want to do. + See parse_condition() + """ + + def __init__(self): + self.error = False + self.mess = "" + pass + + def display(self, e): + self.error = True + if "Undeclared event or state" not in e: + LOGGER.debug("\t" + self.mess + " -> " + e) + def load_solutions(file): """Open a file with many solution/MACs. @@ -59,15 +90,15 @@ def load_solutions(file): sol = "" with open(file, 'r') as fd: for line in fd: - print("line:", line) + LOGGER.debug("Load_solutions :: line: " + line) # Remove possible \t separator from first line (frontier solution) line = line.rstrip('\n').rstrip('\t').replace('\t', ' ') # TODO: remove last space ' ' => beware, may be informative... # (start transitions have no event names: ori="__start__0") line = line.rstrip(' ') if line == '' or line[0] == '=': + # Blank line # Skip blank lines and solution separator in some files (=====) - print('VIDE') continue elif line[0] != '%': if sol == line: @@ -111,31 +142,116 @@ def get_transitions(file): parser = MakeModelFromXmlFile(file) +# print(dir(parser)) +# print(type(parser)) + #<type 'instance'> + #['__doc__', '__init__', '__module__', 'get_model', 'handler', 'model', 'parser' + g = (trans for transition in parser.handler.top_pile.transitions for trans in transition) transitions = defaultdict(list) for trans in g: - print("NEW trans", trans.event) - - # Handle multiple transitions for 1 event - transitions[trans.event].append( - ( - trans.ori.name, trans.ext.name, - { - 'label': trans.event + '[' + trans.condition + ']', - 'condition': trans.condition, - } + + # Get the names of clocks + # Some event have many clocks (like _h_2755) for the same + # ori/ext entities, so we have to extract them + events = re.findall('(_h_[0-9]+)', trans.event) + for event in events: + # LOGGER.debug("NEW trans", event) + + # Handle multiple transitions for 1 event + transitions[event].append( + ( + trans.ori.name, trans.ext.name, + { + 'label': event, #+ '[' + trans.condition + ']', + 'condition': trans.condition, + } + ) ) - ) - #print(transitions) - return transitions + LOGGER.info("{} transitions loaded".format(len(transitions))) + # Return a dict instead of defaultdict to avoid later confusions + #(masked errors) by searching a transition that was not in the model... + return dict(transitions) + -def parse_condition(condition): - """Parse condition string. +def rec(tree, inhibitors_nodes): + """ + tree = ('H', 'v', ( + ('F', 'v', 'G'), + '^', + ( + ('A', 'v', 'B'), + '^', + ('C', 'v', ('D', '^', 'E')) + ) + )) + """ + +# print("TREE", tree, type(tree), dir(tree)) + + if isinstance(tree, str): # terminal node + path = [tree] + solutions = [path] + return solutions + if isinstance(tree, SigNotExpr): + LOGGER.debug("NOT OPERAND: {}, {}".\ + format( + tree.operand, + type(tree.operand) + ) + ) + try: + current_inhibitors = get_places_from_condition(tree.operand.__str__()) + inhibitors_nodes.update(current_inhibitors) + LOGGER.debug("INHIBITORS found: " + str(current_inhibitors)) + + path = [tree.operand.name] + solutions = [path] + return solutions + except AttributeError: + tree = tree.operand + + if isinstance(tree, SigIdentExpr): + path = [tree.name] + solutions = [path] + return solutions + + + + lch = tree.left_h + op = tree.operator + rch = tree.right_h +# print('OZCJSH:', lch, op, rch, sep='\t\t') + lpaths = rec(lch, inhibitors_nodes) + rpaths = rec(rch, inhibitors_nodes) +# print('VFUENK:', lpaths, rpaths) + if op == 'or': # or +# ret = [*lpaths, *rpaths] + ret = list(it.chain(lpaths, rpaths)) +# print('RET:', ret) + return ret + else: # and + assert op == 'and' +# print(list(it.product(lpaths, rpaths))) +# raw_input('test') + + ret = list(l + r for l, r in it.product(lpaths, rpaths)) +# print('RET:', ret) + return ret + + +def get_places_from_condition(condition): + """Parse condition string and return all places, regardless of operators. + + :param: Condition string. + :type: <str> + :return: Set of places. + :rtype: <set> """ replacement = ['and', 'or', 'not', '(', ')'] @@ -144,9 +260,88 @@ def parse_condition(condition): condition = condition.replace(chr, ' ') # Must be exempt of unauthorized chars - print("CONDITION", condition) - return [elem for elem in condition.split(' ') - if elem != ''] + return {elem for elem in condition.split(' ') + if elem != ''} + + +def parse_condition(condition, all_nodes, inhibitors_nodes): + """ + + """ + + LOGGER.debug("CONDITION: " + condition) + # Error Reporter + err = Reporter() + tvi = TableVisitor(err) + # Link the lexer to the model allows to avoid error in Reporter + # like: "-> dec -> Undeclared event or state" + # In practice this is time consuming and useless for what we want to do + #parser = MakeModelFromXmlFile(BIO_MOLDELS_DIR + "Whole NCI-PID database translated into CADBIOM formalism(and).bcx") + #parser.get_model().accept(tvi) + symb_tab = tvi.tab_symb + # Get tree object from condition string + cond_sexpr = compile_cond(condition, symb_tab, err) + # Get all possible paths from the condition + possible_paths = rec(cond_sexpr, inhibitors_nodes) + + # Prune possible paths according to: + # - Inhibitor nodes that must be removed because they will never + # be in the graph. + # - All nodes in transitions (ori -> ext) because we know all transitions + # in the graph, so we know which entities can be choosen to validate a path. + # - All frontier places, that are known entities that can be in conditions + # (not only in ori/ext) of transitions. + # So: authorized nodes = frontier_places + transition_nodes - inhibitor nodes + valid_paths = {tuple(path) for path in possible_paths + if (set(path) - inhibitors_nodes).issubset(all_nodes)} + + # Debugging only + if LOGGER.getEffectiveLevel() == DEBUG: + LOGGER.debug("INHIBIT NODES: " + str(inhibitors_nodes)) + LOGGER.debug("ALL NODES: " + str(all_nodes)) + LOGGER.debug("POSSIBLE PATHS: " + str(possible_paths)) + LOGGER.debug("VALID PATHS: " + str(valid_paths)) + + for path in possible_paths: + pruned_places = set(path) - inhibitors_nodes + isinsubset = pruned_places.issubset(all_nodes) + LOGGER.debug( + "PRUNED PATH: {}, VALID: {}".format( + pruned_places, + isinsubset + ) + ) + + assert len(valid_paths) > 0 + if len(valid_paths) > 1: + LOGGER.warning("Multiple valid paths for: {}:\n{}".format(condition, + valid_paths)) + return valid_paths + + + # condition expressions contains only node ident + icv = SigExpIdCollectVisitor() + lst1 = cond_sexpr.accept(icv) + print(cond_sexpr) + print(type(cond_sexpr)) + print(dir(cond_sexpr)) + print("LISTE", lst1) +# <class 'cadbiom.models.biosignal.sig_expr.SigSyncBinExpr'> +# 'accept', 'get_signals', 'get_ultimate_signals', 'is_bot', 'is_clock', +# 'is_const', 'is_const_false', 'is_ident', 'left_h', 'operator', 'right_h', 'test_equal'] + + print(cond_sexpr.get_signals()) +# print(cond_sexpr.get_ultimate_signals()) + print("LEFT", cond_sexpr.left_h) + print("OPERATOR", cond_sexpr.operator) + print("RIGHT", cond_sexpr.right_h) + + +# ret = treeToTab(cond_sexpr) +# [set([('((formule', True)])] +# print("treeToTab", ret) +# print(type(ret)) +# print(dir(ret)) def build_graph(i, sol, steps, transitions): @@ -161,7 +356,7 @@ def build_graph(i, sol, steps, transitions): :param arg3: A dictionnary of events as keys, and transitions as values (see get_transitions()). :type arg1: <str> - :type arg2: <list> + :type arg2: <list <list>> :type arg3: <dict <list <tuple <str>, <str>, <dict <str>: <str>>>> """ @@ -171,17 +366,23 @@ def build_graph(i, sol, steps, transitions): => Insert a node in a edge. => Link all nodes involved in the condition with this new node. - :param: A list of events (from a step in a solution). + :param: A list of events (transitions) (from a step in a solution). [('Ax', 'n1', {u'label': u'h00[]'}),] :type: <tuple> :return: Fill lists of edges: - edges_with_cond: if transition has a condition + edges_with_cond: link to a transition node for + transition with condition. transition_nodes: add new nodes corresponding to transitions with conditions. - edges_in_cond: Add all edges to nodes linked via the condition. + edges_in_cond: Add all edges to nodes linked to a transition + via the condition (group of nodes involved in the path + choosen by the solver). edges: transition untouched if there is no condition. :rtype: None """ + assert len(step_event) != 0 # Todo: useful ? + + inhibitors_nodes = set() # Inactivated nodes in paths of conditions for trans in step_event: attributes = trans[2] @@ -190,6 +391,7 @@ def build_graph(i, sol, steps, transitions): event = attributes['label'].split('[')[0] if attributes['condition'] != '': + # Add the transition as node transition_nodes.append( ( @@ -198,6 +400,7 @@ def build_graph(i, sol, steps, transitions): 'label': attributes['label'], 'name': attributes['label'], 'color': 'blue', + 'condition': attributes['condition'], } ) ) @@ -223,68 +426,73 @@ def build_graph(i, sol, steps, transitions): ) # Add all transitions to nodes linked via the condition - # TODO: detect inhibition/activation - nodes_in_condition = parse_condition(attributes['condition']) - for node in nodes_in_condition: - edges_in_cond.append( - ( - node, event, - { - 'label': node + '-' + event, - 'color': 'blue', - } + valid_paths = parse_condition( + attributes['condition'], + all_nodes, + inhibitors_nodes + ) + for i, path in enumerate(valid_paths): + for node in path: + edges_in_cond.append( + ( + node, event, + { + 'label': '{} ({})'.format( + event, + i + ), +# 'label': '{} [{}] ({})'.format( +# event, +# ', '.join(path), +# i +# ), #node + '-' + event, + 'color': 'red' if node in inhibitors_nodes else 'green', + } + ) ) - ) else: # Normal edges edges.append(trans) -# return trans - print("BUILD GRAPH FOR SOL:", sol) - print("STEPS:", steps) + # Get & make all needed edges ############################################## + LOGGER.info("BUILD GRAPH FOR SOL: " + str(sol)) + LOGGER.debug("STEPS: " + str(steps)) +# +# print(transitions['_h_2755']) +# print(transitions['_h_4716']) +# print(transitions['_h_2206']) +# print(transitions['_h_3426']) +# exit() frontier_places = sol.split(' ') - edges_with_cond = list() - edges_in_cond = list() - transition_nodes = list() - edges = list() - # Get & make all needed edges - - [filter_transitions(transitions[step_event]) for step_event in it.chain(*steps)] - - for step_event in it.chain(*steps): - event = transitions[step_event] - # print(step_event) - if len(event) == 0: - print("ERROR for event: " + str(event) + '\n') - # TODO: handle this - # Add the transition as node - transition_nodes.append( - ( - "ERROR: " + str(step_event), - { - 'label': step_event, - 'name': step_event, - 'color': 'yellow', - } - ) - ) - else: - filter_transitions(event) - -# edges = [edge for edge in g if edge is not None] - - print("edges without cond", edges) - print("edges with cond", edges_with_cond) - print("transition nodes added", transition_nodes) - + edges_with_cond = list() # Edges between ori <-> transition node <-> ext + edges_in_cond = list() # Edges between transition node and nodes in condition + transition_nodes = list() # Nodes inserted because of condition in transition + edges = list() # Normal transitions without condition + + # Get all nodes in all transitions (origin & ext) + all_transitions = (transitions[step_event] for step_event in it.chain(*steps)) + transitions_ori_ext = (tuple((trans[0], trans[1])) for trans in it.chain(*all_transitions)) + all_nodes = set(it.chain(*transitions_ori_ext)) | set(frontier_places) + + # Parse all conditions in transitions; + # add nodes in conditions and transition nodes + [filter_transitions(transitions[step_event]) + for step_event in it.chain(*steps)] + +# print("edges without cond", edges) +# print("edges with cond", edges_with_cond) +# print("transition nodes added", transition_nodes) +# raw_input("PAUSE") # Make Graph ############################################################### G = nx.DiGraph() + # Add all nodes (some frontier places are in this set) + G.add_nodes_from(all_nodes, color='grey') # Add fontier places - G.add_nodes_from(frontier_places, node_color='red') - # Add all nodes + G.add_nodes_from(frontier_places, color='red') + # Add all transition nodes G.add_nodes_from(transition_nodes, color='blue') # Node attribute ? @@ -296,21 +504,6 @@ def build_graph(i, sol, steps, transitions): G.add_edges_from(edges_in_cond) - unzip = lambda l: list(zip(*l)) - - # Get a list of nodes (without dictionnary of attributes) - unziped_transition_nodes = unzip(transition_nodes)[0] - - def color_map(node): - # print("color for:", node) - if node in frontier_places: - return 'red' - if node in unziped_transition_nodes: - return 'blue' - else: - return 'white' - - # Drawing ################################################################## # draw_circular(G, **kwargs) On a circle. # draw_random(G, **kwargs) Uniformly at random in the unit square. @@ -318,6 +511,8 @@ def build_graph(i, sol, steps, transitions): # draw_spring(G, **kwargs) Fruchterman-Reingold force-directed algorithm. # draw_shell(G, **kwargs) Concentric circles. # draw_graphviz(G[, prog]) Draw networkx graph with graphviz layout. + unzip = lambda l: list(zip(*l)) + pos = nx.circular_layout(G) # Legend of conditions in transition nodes @@ -329,11 +524,25 @@ def build_graph(i, sol, steps, transitions): ax.text(0, 0, text, style='italic', fontsize=10, bbox={'facecolor':'white', 'alpha':0.5, 'pad':10}) - # Draw nodes: # - red: frontier places (in solution variable), # - white: middle edges, # - blue: transition edges + def color_map(node): + # print("color for:", node) + if node in frontier_places: # Test first (see cond below) + return 'red' + if node in unziped_transition_nodes: + return 'blue' + if node in all_nodes: # some /all frontier places are in this set + return 'grey' + else: + return 'white' + + # Get a list of transition nodes (without dictionnary of attributes) + unziped_transition_nodes = unzip(transition_nodes)[0] + + # Color nodes colors = [color_map(node) for node in G.nodes_iter()] nx.draw(G, pos=pos, with_labels=True, node_color=colors, node_size=1000, alpha=0.5, ax=ax) @@ -354,7 +563,10 @@ def build_graph(i, sol, steps, transitions): # plt.savefig(GRAPHS_DIR + date + '_' + sol[:75] + ".svg", format="svg") # plt.show() - nx.write_graphml(G, GRAPHS_DIR + date + '_' + str(i) +'_' + sol[:75] + ".graphml") + nx.write_graphml( + G, + GRAPHS_DIR + date + '_' + str(i) +'_' + sol[:75] + ".graphml" + ) #nx.write_gml(G, date + '_' + sol + ".gml") @@ -438,9 +650,23 @@ def test_main(): -def sol_repr(sol_steps, transitions): - """ +def sol_digging(sol_steps, transitions): + """Get steps and all transitions and write all data for each step in a file. + This is an exemple to quickly search all transition attributes involved in a + complete MAC. + + .. note:: Output file: output.txt + + :param arg1: List of steps involved in a solution. See load_solutions(). + :param arg2: A dictionnary of events as keys, and transitions as values. + Since many transitions can define an event, values are lists. + Each transition is a tuple with: origin node, final node, attributes + like label and condition. + {u'h00': [('Ax', 'n1', {u'label': u'h00[]'}),] + See get_transitions(). + :type arg1: <list> + :type arg2: <dict <list <tuple <str>, <str>, <dict <str>: <str>>>> """ def write_transition_def(step_event): @@ -481,6 +707,7 @@ def sol_repr(sol_steps, transitions): # Sol separation fd.write('\n====================================================\n') + def main(model_file, solution_file): """ """ @@ -492,6 +719,16 @@ def main(model_file, solution_file): if __name__ == "__main__": +# cond = "((((CXCL10_CXCR3_intToMb and not(CCL11_CXCR3_intToMb)))or((CXCL10_CXCR3_intToMb and not(CXCL13_CXCR3_intToMb))))or(CXCL9_11_CXCR3_active_intToMb))or(CXCL4_CXCR3_intToMb)" +# cond = "((((A and not(B)))or((A and not(C))))or(D))or(E)" +# cond = "(A and not(B))" +# cond = "(A and not(B))" +# cond = "(((((A)or(B))or(C))or(B))or(D))or(E)" +# cond = "((((not(ATF2_JUND_macroH2A_nucl))or(Fra1_JUND_active_nucl))or(Fra1_JUN_active_nucl))or(TCF4_betacatenin_active_nucl))or(JUN_FOS_active_nucl)" +# cond = "((A and B and C)) and (not((D and E and F and G)))" +# ret = parse_condition(cond) +# print(ret) + process_solutions(load_solutions(LOG_DIR + "../run/Whole NCI-PID database translated into CADBIOM formalism(and)_SRP9_cam_complete.txt"), get_transitions(BIO_MOLDELS_DIR + "Whole NCI-PID database translated into CADBIOM formalism(and).bcx")) exit() @@ -512,11 +749,11 @@ if __name__ == "__main__": # print(g) # exit() - sol_repr(load_solutions(LOG_DIR + "../run/pid_and_clock_no_perm_p21corrected_start_SRP9_complete.txt"), + sol_digging(load_solutions(LOG_DIR + "../run/pid_and_clock_no_perm_p21corrected_start_SRP9_complete.txt"), get_transitions(BIO_MOLDELS_DIR + "Whole NCI-PID database translated into CADBIOM formalism(and).bcx")) exit() - sol_repr(load_solutions(LOG_DIR + "sols_new_solver.txt"), + sol_digging(load_solutions(LOG_DIR + "sols_new_solver.txt"), get_transitions(BIO_MOLDELS_DIR + "mini_test_publi.bcx")) exit() process_solutions(load_solutions(LOG_DIR + "sols_new_solver.txt"), -- GitLab