From 21b3c639075fc1d3d799261483f9be0d1ad570c0 Mon Sep 17 00:00:00 2001
From: vgolubev <nakmak1998@gmail.com>
Date: Sun, 31 May 2020 19:08:18 +0300
Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?=
 =?UTF-8?q?=D0=BF=D1=80=D0=B5=D0=B7=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8?=
 =?UTF-8?q?=D1=8E=20=D0=B3=D1=80=D0=B0=D1=84=D0=BE=D0=B2=D0=BE=D0=B9=20?=
 =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B8=20=D0=B2=20=D0=B2=D0=B8?=
 =?UTF-8?q?=D0=B4=D0=B5=20=D1=81=D0=BB=D0=BE=D0=B2=D0=B0=D1=80=D1=8F=20?=
 =?UTF-8?q?=D0=B2=20GraphFactory?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Данный функционал позволяет отправлять на клиент структурное описание графовой модели для последующей визуализации и интерактивной работы.
Граф представляется в виде словаря со следующей структурой:
graph: {
	'init_state_name': {
		'subgraph': None/{subgraph},
		'connect_to': [{'next': next_state_name, 'morph_f': morphism, 'pred_f': predicate}, ...] // здесь отобразаются связи вершины с другими вершинами
	},
	'state_name2': {...},
	...
	'term_state_name': {...}
}

Данный словарь заполняется во время парсинга adot-файла. Не зависит от режима парсинга: c генарацией cpp или без.
---
 comsdk/graph.py  | 164 +++++++++++++++++++++++++----------------------
 comsdk/parser.py |  35 ++++++++--
 2 files changed, 119 insertions(+), 80 deletions(-)

diff --git a/comsdk/graph.py b/comsdk/graph.py
index 99e9af7..79a4caf 100644
--- a/comsdk/graph.py
+++ b/comsdk/graph.py
@@ -1,13 +1,14 @@
 import collections
+import importlib as imp
 import os
 from enum import Enum, auto
 from functools import partial
-import importlib as imp
-
 
 import pycomsdk.comsdk.comaux as aux
 
-ImplicitParallelizationInfo = collections.namedtuple('ImplicitParallelizationInfo', ['array_keys_mapping', 'branches_number', 'branch_i'])
+ImplicitParallelizationInfo = collections.namedtuple('ImplicitParallelizationInfo',
+                                                     ['array_keys_mapping', 'branches_number', 'branch_i'])
+
 
 class Func():
     __slots__ = (
@@ -16,42 +17,45 @@ class Func():
         'comment',
         'name'
     )
-    def __init__(self, module="", name="", dummy=False,func=None, comment=''):
+
+    def __init__(self, module="", name="", dummy=False, func=None, comment=''):
         self.module = module
         self.name = name
-        self.comment=comment.replace("\0", " ") if comment is not None else ""
-        if module =="" or name =="" or module is None or name is None:
+        self.comment = comment.replace("\0", " ") if comment is not None else ""
+        if module == "" or name == "" or module is None or name is None:
             dummy = True
         if func is not None:
             self.func = func
         elif dummy:
             self.func = lambda data: data
-        else: 
-            print("LOADING function {} from {} module".format(name, module) )
+        else:
+            print("LOADING function {} from {} module".format(name, module))
             try:
                 self.func = getattr(imp.import_module(module), name)
             except Exception:
                 raise Exception("Could not load function {} from {} module".format(name, module))
 
     def __str__(self):
-        if self.module =="" or self.name =="":
+        if self.module == "" or self.name == "":
             return ''
         return "{}_{}".format(self.module, self.name)
 
+
 class Selector(Func):
     def __init__(self, ntransf, module="", name="", dummy=False):
-        if module=="" and name =="":
+        if module == "" and name == "":
             dummy = True
         self.dummy = dummy
         super().__init__(module, name, func=(lambda x: [True for i in range(ntransf)]) if dummy else None)
+
     def __str__(self):
-        if self.module =="" or self.name =="":
+        if self.module == "" or self.name == "":
             return ''
         return "{}_{}".format(self.module, self.name)
 
 
 class Transfer:
-    def __init__(self, edge, output_state, order=0, comment = None):
+    def __init__(self, edge, output_state, order=0, comment=None):
         self.edge = edge
         self.output_state = output_state
         self.order = order
@@ -60,10 +64,12 @@ class Transfer:
         self.edge.morph(data, dynamic_keys_mapping)
         return self.output_state
 
+
 class IdleRunType(Enum):
     INIT = auto()
     CLEANUP = auto()
 
+
 class PluralState:
     def __init__(self, states):
         self.states = states
@@ -73,10 +79,12 @@ class PluralState:
         for init_state, term_state in zip(self.states, term_states):
             init_state.transfers.append(Transfer(edge, term_state))
 
+
 class Graph:
     '''
     Class describing a graph-based computational method. Graph execution must start from this object.     
     '''
+
     def __init__(self, init_state,
                  term_state=None,
                  ):
@@ -100,15 +108,15 @@ class Graph:
         cur_state = self.init_state
         implicit_parallelization_info = None
         while cur_state is not None:
- #           print('1) In main loop', implicit_parallelization_info)
-#            morph = _run_state(cur_state, data, implicit_parallelization_info)
+            #           print('1) In main loop', implicit_parallelization_info)
+            #            morph = _run_state(cur_state, data, implicit_parallelization_info)
             transfer_f, implicit_parallelization_info = _run_state(cur_state, data, implicit_parallelization_info)
-#            print('2) In main loop', implicit_parallelization_info)
+            #            print('2) In main loop', implicit_parallelization_info)
             if '__EXCEPTION__' in data:
                 return False
-#            cur_state, implicit_parallelization_info = morph(data)
+            #            cur_state, implicit_parallelization_info = morph(data)
             cur_state = transfer_f(data)
-#            print(morph)
+            #            print(morph)
             if '__EXCEPTION__' in data:
                 return False
         return True
@@ -124,12 +132,10 @@ class Graph:
             data['__WORKING_DIR__'] = data['__CURRENT_WORKING_DIR__']
 
 
-
-
 class State:
     __slots__ = [
         'name',
-        'input_edges_number', #output_edges_number == len(transfers)
+        'input_edges_number',  # output_edges_number == len(transfers)
         'looped_edges_number',
         'activated_input_edges_number',
         'transfers',
@@ -141,11 +147,13 @@ class State:
         '_proxy_state',
         'possible_branches',
         'comment'
-        ]
-    def __init__(self, name, 
+    ]
+
+    def __init__(self, name,
                  parallelization_policy=None,
                  selector=None,
-                 array_keys_mapping=None, # if array_keys_mapping is not None, we have implicit parallelization in this state
+                 array_keys_mapping=None,
+                 # if array_keys_mapping is not None, we have implicit parallelization in this state
                  ):
         self.name = name
         self.parallelization_policy = SerialParallelizationPolicy() if parallelization_policy is None else parallelization_policy
@@ -155,19 +163,17 @@ class State:
         self.looped_edges_number = 0
         self.activated_input_edges_number = 0
         self.transfers = []
-        self.possible_branches=[]
-        self.is_term_state=False
+        self.possible_branches = []
+        self.is_term_state = False
         self._branching_states_history = None
-        self._proxy_state=None
+        self._proxy_state = None
         self.comment = None
 
     def idle_run(self, idle_run_type, branching_states_history):
         def __sort_by_order(tr):
             return tr.edge.order
-        self.transfers.sort(key = __sort_by_order)
-        # print(self.name)
-        # for t in self.transfers:
-            # print("\t", t.edge.order, t.edge.pred_name, t.edge.morph_name)
+
+        self.transfers.sort(key=__sort_by_order)
         if self._proxy_state is not None:
             return self._proxy_state.idle_run(idle_run_type, branching_states_history)
         if idle_run_type == IdleRunType.INIT:
@@ -175,7 +181,7 @@ class State:
             if self.input_edges_number != 1:
                 if self._is_looped_branch(branching_states_history):
                     self.looped_edges_number += 1
-                return # no need to go further if we already were there
+                return  # no need to go further if we already were there
             if self._branching_states_history is None:
                 self._branching_states_history = branching_states_history
         elif idle_run_type == IdleRunType.CLEANUP:
@@ -186,9 +192,7 @@ class State:
             if self._branching_states_history is None:
                 self._branching_states_history = branching_states_history
         else:
-            self.activated_input_edges_number += 1 # BUG: here we need to choose somehow whether we proceed or not
-        # if len(self.transfers) == 0:
-            # print('Terminate state found')
+            self.activated_input_edges_number += 1  # BUG: here we need to choose somehow whether we proceed or not
         if len(self.transfers) == 1:
             self.transfers[0].output_state.idle_run(idle_run_type, branching_states_history)
         else:
@@ -201,26 +205,23 @@ class State:
             self.comment = comment
         self.transfers.append(Transfer(edge, term_state))
         self.selector = Selector(len(self.transfers))
-#        edge.set_output_state(term_state)
-#        self.output_edges.append(edge)
 
     def replace_with_graph(self, graph):
         self._proxy_state = graph.init_state
         graph.term_state.transfers = self.transfers
         graph.term_state.selector = self.selector
 
-    
     def run(self, data, implicit_parallelization_info=None):
-        print('STATE {}\n\tjust entered, implicit_parallelization_info: {}'.format(self.name, implicit_parallelization_info))
-        # print('\t{}'.format(data))
+        print('STATE {}\n\tjust entered, implicit_parallelization_info: {}'.format(self.name,
+                                                                                   implicit_parallelization_info))
         if self._proxy_state is not None:
             return self._proxy_state.run(data, implicit_parallelization_info)
         self._activate_input_edge(implicit_parallelization_info)
-        #self.activated_input_edges_number += 1
-        print('\trequired input: {}, active: {}, looped: {}'.format(self.input_edges_number, self.activated_input_edges_number, self.looped_edges_number))
-#        print('qwer')
+        print('\trequired input: {}, active: {}, looped: {}'.format(self.input_edges_number,
+                                                                    self.activated_input_edges_number,
+                                                                    self.looped_edges_number))
         if not self._ready_to_transfer(implicit_parallelization_info):
-            return None, None # it means that this state waits for some incoming edges (it is a point of collision of several edges)
+            return None, None  # it means that this state waits for some incoming edges (it is a point of collision of several edges)
         self._reset_activity(implicit_parallelization_info)
         if self.is_term_state:
             implicit_parallelization_info = None
@@ -231,16 +232,20 @@ class State:
         if not selected_edges:
             raise GraphUnexpectedTermination(
                 "STATE {}: error in selector: {} ".format(self.name, selected_edges))
-        selected_transfers = [self.transfers[i] for i, _ in enumerate(selected_edges) if selected_edges[i]==True]
+        selected_transfers = [self.transfers[i] for i, _ in enumerate(selected_edges) if selected_edges[i] == True]
         for transf in selected_transfers:
             if not transf.edge.predicate(data, dynamic_keys_mapping):
-                raise Exception("\tERROR: predicate {} returns {} running from state {}\n data{}".format(transf.edge.pred_f.name,transf.edge.predicate(data, dynamic_keys_mapping), self.name, data))
+                raise Exception(
+                    "\tERROR: predicate {} returns {} running from state {}\n data{}".format(transf.edge.pred_f.name,
+                                                                                             transf.edge.predicate(data,
+                                                                                                                   dynamic_keys_mapping),
+                                                                                             self.name, data))
         return self.parallelization_policy.make_transfer_func(selected_transfers,
-                                                         array_keys_mapping=self.array_keys_mapping,
-                                                         implicit_parallelization_info=implicit_parallelization_info, state=self), \
+                                                              array_keys_mapping=self.array_keys_mapping,
+                                                              implicit_parallelization_info=implicit_parallelization_info,
+                                                              state=self), \
                implicit_parallelization_info
 
-
     def _activate_input_edge(self, implicit_parallelization_info=None):
         if implicit_parallelization_info is None or self.is_term_state:
             self.activated_input_edges_number += 1
@@ -255,16 +260,17 @@ class State:
             if self.is_term_state:
                 required_activated_input_edges_number = implicit_parallelization_info.branches_number
                 return self.activated_input_edges_number == required_activated_input_edges_number
-            return self.activated_input_edges_number[implicit_parallelization_info.branch_i] == required_activated_input_edges_number
+            return self.activated_input_edges_number[
+                       implicit_parallelization_info.branch_i] == required_activated_input_edges_number
         else:
             return self.activated_input_edges_number == required_activated_input_edges_number
 
-#        if implicit_parallelization_info is None or self.is_term_state:
-#            if self.is_term_state:
-#                required_activated_input_edges_number = implicit_parallelization_info.branches_number
-#            return self.activated_input_edges_number == required_activated_input_edges_number
-#        else:
-#            return self.activated_input_edges_number[implicit_parallelization_info.branch_i] == required_activated_input_edges_number
+    #        if implicit_parallelization_info is None or self.is_term_state:
+    #            if self.is_term_state:
+    #                required_activated_input_edges_number = implicit_parallelization_info.branches_number
+    #            return self.activated_input_edges_number == required_activated_input_edges_number
+    #        else:
+    #            return self.activated_input_edges_number[implicit_parallelization_info.branch_i] == required_activated_input_edges_number
 
     def _reset_activity(self, implicit_parallelization_info=None):
         self._branching_states_history = None
@@ -274,7 +280,7 @@ class State:
             else:
                 self.activated_input_edges_number[implicit_parallelization_info.branch_i] -= 1
         else:
-#            self.activated_input_edges_number = 0
+            #            self.activated_input_edges_number = 0
             if implicit_parallelization_info is None or self.is_term_state:
                 self.activated_input_edges_number = 0
             else:
@@ -290,23 +296,25 @@ class State:
 def transfer_to_termination(data):
     return None
 
+
 class SerialParallelizationPolicy:
-#    def __init__(self, data):
-#        self.data = data
+    #    def __init__(self, data):
+    #        self.data = data
     def __init__(self):
         pass
 
     def make_transfer_func(self, morphisms, array_keys_mapping=None, implicit_parallelization_info=None, state=None):
         def _morph(data):
-            # print("MORPHING FROM {}".format(state.name))
             if array_keys_mapping is None:
                 dynamic_keys_mapping = build_dynamic_keys_mapping(implicit_parallelization_info)
-                next_morphs = [partial(morphism.transfer, dynamic_keys_mapping=dynamic_keys_mapping) for morphism in morphisms]
+                next_morphs = [partial(morphism.transfer, dynamic_keys_mapping=dynamic_keys_mapping) for morphism in
+                               morphisms]
                 next_impl_para_infos = [implicit_parallelization_info for _ in morphisms]
- #               print('\t\t {}'.format(implicit_parallelization_infos))
             else:
                 if len(morphisms) != 1:
-                    raise BadGraphStructure('Impossible to create implicit paralleilzation in the state with {} output edges'.format(len(morphisms)))
+                    raise BadGraphStructure(
+                        'Impossible to create implicit paralleilzation in the state with {} output edges'.format(
+                            len(morphisms)))
                 dynamic_keys_mapping = build_dynamic_keys_mapping(implicit_parallelization_info)
                 proxy_data = aux.ProxyDict(data, keys_mappings=array_keys_mapping)
                 anykey = next(iter(array_keys_mapping.keys()))
@@ -314,49 +322,53 @@ class SerialParallelizationPolicy:
                 next_morphs = []
                 next_impl_para_infos = []
                 for branch_i in range(implicit_branches_number):
-                    implicit_parallelization_info_ = ImplicitParallelizationInfo(array_keys_mapping, implicit_branches_number, branch_i)
+                    implicit_parallelization_info_ = ImplicitParallelizationInfo(array_keys_mapping,
+                                                                                 implicit_branches_number, branch_i)
                     dynamic_keys_mapping = build_dynamic_keys_mapping(implicit_parallelization_info_)
-#                    print(dynamic_keys_mapping)
                     next_morphs.append(partial(morphisms[0].morph, dynamic_keys_mapping=dynamic_keys_mapping))
                     next_impl_para_infos.append(implicit_parallelization_info_)
             cur_morphs = []
             cur_impl_para_infos = []
-            #while len(next_morphs) != 1 or _is_implicitly_parallelized(next_impl_para_infos):
-            while len(next_morphs) != 1 or _requires_joint_of_implicit_parallelization(array_keys_mapping, next_impl_para_infos):
+            # while len(next_morphs) != 1 or _is_implicitly_parallelized(next_impl_para_infos):
+            while len(next_morphs) != 1 or _requires_joint_of_implicit_parallelization(array_keys_mapping,
+                                                                                       next_impl_para_infos):
                 if next_impl_para_infos == []:
                     raise Exception("Morphs count on state {} is {}".format(state.name, str(len(next_morphs))))
-#                print(array_keys_mapping, next_impl_para_infos)
+                #                print(array_keys_mapping, next_impl_para_infos)
                 cur_morphs[:] = next_morphs[:]
                 cur_impl_para_infos[:] = next_impl_para_infos[:]
                 del next_morphs[:]
                 del next_impl_para_infos[:]
                 for morph, impl_para_info in zip(cur_morphs, cur_impl_para_infos):
                     next_state = morph(data)
-#                    print('\t next_state: {}, with impl para info: {}'.format(next_state.name, impl_para_info))
+                    #                    print('\t next_state: {}, with impl para info: {}'.format(next_state.name, impl_para_info))
                     if next_state is None:
                         return None
                     next_morph, next_impl_para_info = _run_state(next_state, data, impl_para_info)
-#                    print('\t next_morph: {}'.format(next_morph))
+                    #                    print('\t next_morph: {}'.format(next_morph))
                     if '__EXCEPTION__' in data:
                         return None
                     if next_morph is not None:
                         next_morphs.append(next_morph)
                         next_impl_para_infos.append(next_impl_para_info)
-#                print(array_keys_mapping, next_impl_para_infos)
-                #print(len(next_morphs))
-#            print('\t last morph: {}'.format(next_morphs[0]))
+            #                print(array_keys_mapping, next_impl_para_infos)
+            # print(len(next_morphs))
+            #            print('\t last morph: {}'.format(next_morphs[0]))
             next_state = next_morphs[0](data)
-#            print(next_state.name, next_impl_para_infos[0])
+            #            print(next_state.name, next_impl_para_infos[0])
             return next_state
+
         return _morph
 
 
 class BadGraphStructure(Exception):
     pass
 
+
 class GraphUnexpectedTermination(Exception):
     pass
 
+
 def _requires_joint_of_implicit_parallelization(array_keys_mapping, impl_para_infos):
     if array_keys_mapping is None:
         return False
@@ -365,10 +377,12 @@ def _requires_joint_of_implicit_parallelization(array_keys_mapping, impl_para_in
             return True
     return False
 
+
 def _get_trues(boolean_list):
     return [i for i, val in enumerate(boolean_list) if val == True]
 
-#def _run_state(state, data, implicit_parallelization_info=None):
+
+# def _run_state(state, data, implicit_parallelization_info=None):
 #    try:
 #        next_morphism = state.run(data, implicit_parallelization_info)
 #    except GraphUnexpectedTermination as e:
@@ -391,4 +405,4 @@ def build_dynamic_keys_mapping(implicit_parallelization_info=None):
     dynamic_keys_mapping = {}
     for key, keys_path in implicit_parallelization_info.array_keys_mapping.items():
         dynamic_keys_mapping[key] = aux.ArrayItemGetter(keys_path, implicit_parallelization_info.branch_i)
-    return dynamic_keys_mapping
\ No newline at end of file
+    return dynamic_keys_mapping
diff --git a/comsdk/parser.py b/comsdk/parser.py
index a47cc11..bde9f7c 100644
--- a/comsdk/parser.py
+++ b/comsdk/parser.py
@@ -6,7 +6,7 @@ from pycomsdk.comsdk.edge import Edge
 from pycomsdk.comsdk.graph import Graph, Func, State, Selector
 
 
-class Params():
+class Params:
     __slots__ = (
         'module',
         'entry_func',
@@ -33,28 +33,42 @@ class Params():
 
 # entities = {}
 
-class GraphFactory():
+class GraphFactory:
     __slots__ = (
         'name',
         'states',
         'graph',
         'issub',
         'tocpp',
-        'entities'
+        'entities',
+        'graph_structure'
     )
 
     def __init__(self, tocpp=False):
+        """
+        Функция инициализации. Существует два режима конфигурации:
+            1) Без генерации cpp-кода
+            2) Генерация cpp-кода
+
+        :param tocpp: переключатель режима работы фабрики
+        """
         self.states = {}
         self.entities = {}
         self.tocpp = tocpp
         self.name = None
         self.issub = False
+        self.graph_structure = {} # dict-representation of graph
 
     def add_state(self, statename):
         if statename not in self.states:
             self.states[statename] = State(statename)
             if statename in self.entities:
                 self.states[statename].comment = self.entities[statename].comment
+        self.graph_structure.update({statename: {
+            'subgraph': None,
+            'connect_to': [],
+            'graph_name': self.name
+        }})
 
     def _create_morphism(self, morphname=None):
         comment = ""
@@ -91,6 +105,11 @@ class GraphFactory():
     def add_connection(self, st1, st2, morphism=None, ordr=0):
         pred, entr, comm = self._create_morphism(morphism)
         self.states[st1].connect_to(self.states[st2], edge=Edge(pred, entr, order=ordr, comment=comm))
+        self.graph_structure[st1]['connect_to'].append({
+            'next': st2,
+            'pred_f': str(pred),
+            'morph_f': str(entr),
+        })
         print("{} -> {}".format(st1, st2))
 
     def build(self, nsub):
@@ -123,15 +142,22 @@ class GraphFactory():
             else:
                 self.states[s].selector = Selector(len(self.states[s].transfers))
             if s in self.entities and self.entities[s].subgraph is not None:
+                # print(s + " is subgraph")
                 print("Replacing state {} with subgraph {}".format(s, self.entities[s].subgraph))
                 parsr = Parser(subgraph=True, tocpp=self.tocpp)
                 subgr = parsr.parse_file(self.entities[s].subgraph)
                 self.states[s].replace_with_graph(subgr)
+                self.graph_structure[s]['subgraph'] = {
+                    'graph': parsr.fact.graph_structure,
+                    'init_state': subgr.init_state.name,
+                    'term_state': subgr.term_state.name,
+                    'subgraph_name': parsr.fact.name
+                }
                 self.graph = Graph(self.graph.init_state, self.graph.term_state)
         return self.graph
 
 
-class Parser():
+class Parser:
     __slots__ = (
         'fact',
         'issub'
@@ -462,7 +488,6 @@ class _Bush():
         res = ""
         for tr in self.branches[i]:
             edge = tr.edge
-            print(tr.output_state.name)
             if edge.comment != "":
                 res += "\t//{}\n".format(edge.comment)
             if edge.pred_f.name != "":
-- 
2.17.1