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