Ver código fonte

Updated himesis

rparedis 2 anos atrás
pai
commit
daa9a0c4ec
4 arquivos alterados com 482 adições e 264 exclusões
  1. 447 263
      core/himesis.py
  2. 34 0
      state/__init__.py
  3. BIN
      state/test.png
  4. 1 1
      state/test/fixtures/state.py

+ 447 - 263
core/himesis.py

@@ -1,67 +1,76 @@
-'''This file is part of AToMPM - A Tool for Multi-Paradigm Modelling
-Copyright 2011 by the AToMPM team and licensed under the LGPL
-See COPYING.lesser and README.md in the root of this project for full details'''
+"""
+Core logic for Himesis. Based on the code in AToMPM and earlier work by Marc Provost.
 
-import uuid, copy, igraph as ig
+Himesis is a hierarchical, directed and labelled graph kernel, built on top of the ModelVerse State.
+The name Himesis has etymological roots from genesis for "origin", mimesis for "representation" and
+the syllable "Hi" stands for "Hierarchical".
+
+.. See Also:
+    http://modelverse.uantwerpen.be/people/mprovost/projects/himesis/index.html
+"""
+
+import uuid, copy
+from state.base import State
 
 
 class HConstants:
-    '''
-        Himesis constants must start with '$' to ensure there are no name clashes 
-        with user-attributes (which are prohibited from starting with '$')
-    '''
-    GUID                      = '$GUID__'
-    METAMODELS              = '$mms__'
-    MISSING_METAMODELS = '$missing_mms__'
-    FULLTYPE               = '$ft__'
+    """
+    Himesis constants must start with '$' to ensure there are no name clashes
+    with user-attributes (which are prohibited from starting with '$')
+    """
+    GUID                = '$GUID__'
+    METAMODELS          = '$mms__'
+    MISSING_METAMODELS  = '$missing_mms__'
+    FULLTYPE            = '$ft__'
     CONNECTOR_TYPE      = '$ct__'
-    MT_LABEL              = '$MT_label__'
-    MT_CONSTRAINT          = '$MT_constraint__'
-    MT_ACTION              = '$MT_action__'
-    MT_SUBTYPE_MATCH      = '$MT_subtypeMatching__'
-    MT_SUBTYPES          = '$MT_subtypes__'
-    MT_DIRTY              = '$MT_dirty__'
-    MT_PIVOT_IN          = '$MT_pivotIn__'
-    MT_PIVOT_OUT         = '$MT_pivotOut__'
+    ATTRIBUTES          = "$attr__"
+    MT_LABEL            = '$MT_label__'
+    MT_CONSTRAINT       = '$MT_constraint__'
+    MT_ACTION           = '$MT_action__'
+    MT_SUBTYPE_MATCH    = '$MT_subtypeMatching__'
+    MT_SUBTYPES         = '$MT_subtypes__'
+    MT_DIRTY            = '$MT_dirty__'
+    MT_PIVOT_IN         = '$MT_pivotIn__'
+    MT_PIVOT_OUT        = '$MT_pivotOut__'
 
 
 
-class Himesis(ig.Graph):
+class Himesis:
     """
-        Creates a typed, attributed, directed, multi-graph.
-        @param num_nodes: the total number of nodes. If not known, you can add more vertices later
-        @param edges: the list of edges where each edge is a tuple representing the ids of the source and target nodes
+    Creates a typed, attributed, directed, hierarchical, multi-graph.
+
+    Args:
+        name (str):     The name of this graph.
+        mvs (State):    The backend to use for the graph representations.
     """
     Constants = HConstants
     EDGE_LIST_THRESHOLD = 10**3
 
-
-    @staticmethod
-    def is_RAM_attribute(attr_name):
-        return not attr_name.startswith('$')
-
-    def __init__(self, name='', num_nodes=0, edges=[]):
-        """
-            Creates a typed, attributed, directed, multi-graph.
-            @param name: the name of this graph
-            @param num_nodes: the total number of nodes. If not known, you can add more vertices later
-            @param edges: the list of edges where each edge is a tuple representing the ids of the source and target nodes
-        """
-        ig.Graph.__init__(self, directed=True, n=num_nodes, edges=edges)
+    def __init__(self, name, mvs):
         if not name:
             name = self.__class__.__name__
         self.name = name
 
+        self.state = mvs
+        self.attrib = {}
+        self.vcount = 0
+        self.edges = []
+
         # mmTypeData: enables add_node() to properly initialize to-be nodes s.t. they reflect the default values specified by their metamodels
-        # _guid2index: a fast lookup of the node's index by its guid
+        # _index2guid: a fast lookup of the node's guid by its index
         # session: area which provides a clean and efficient way to remember information across rules
         self.mmTypeData = {}
-        self._guid2index = {}
+        self._index2guid = {}
         self.session = {}
 
+    @staticmethod
+    def is_RAM_attribute(attr_name):
+        return not attr_name.startswith('$')
+
     def copy(self):
-        cpy = ig.Graph.copy(self)
-        cpy._guid2index = copy.deepcopy(self._guid2index)
+        mvs = copy.deepcopy(self.state)
+        cpy = Himesis(self.name, mvs)
+        cpy._index2guid = copy.deepcopy(self._index2guid)
         ''' hergin :: motif-integration FIX for mmTypeData bug '''
         cpy.mmTypeData = copy.deepcopy(self.mmTypeData)
         cpy.session = copy.deepcopy(self.session)
@@ -75,148 +84,268 @@ class Himesis(ig.Graph):
     def __deepcopy__(self, memo):
         return self.__copy__()
 
+    def __getitem__(self, item):
+        return self.attrib[item]
+
+    def __setitem__(self, key, value):
+        self.attrib[key] = value
+
     def __str__(self):
         s = super(Himesis, self).__str__()
         return self.name + ' ' + s[s.index('('):] + ' ' + str(self[Himesis.Constants.GUID])
 
     def get_id(self):
         """
-            Returns the unique identifier of the graph
+        Returns the unique identifier of the graph
         """
         return self[Himesis.Constants.GUID]
 
+    def vertex_count(self):
+        return len(self._index2guid)
+
+    def edge_count(self):
+        return len(self.edges)
+
     def node_iter(self):
         """
-            Iterates over the nodes in the graph, by index
+        Iterates over the nodes in the graph, by index
+        """
+        return range(self.vertex_count())
+
+    def node_guid_iter(self):
+        """
+        Iterates over the nodes in the graph, by guid index
         """
-        return range(self.vcount())
+        return (self.get_node(ix) for ix in self.node_iter())
 
     def edge_iter(self):
         """
-            Iterates over the edges in the graph, by index
+        Iterates over the edges in the graph, by index
         """
-        return range(self.ecount())
-
-    def add_node(self, fulltype=None, isConnector=None, newNodeGuid=None):
-        newNodeIndex = self.vcount()
-        if newNodeGuid == None :
-            newNodeGuid = uuid.uuid4()
-        self.add_vertices(1)
-        self.vs[newNodeIndex][Himesis.Constants.GUID] = newNodeGuid
-        self.vs[newNodeIndex][Himesis.Constants.FULLTYPE] = fulltype
-        self.vs[newNodeIndex][Himesis.Constants.CONNECTOR_TYPE] = isConnector
+        return iter(self.edges)
+
+    def follow_edge(self, node, label):
+        return self.state.read_dict(node, label)
+
+    def node_set_attribute(self, node, key, value):
+        if Himesis.Constants.ATTRIBUTES not in self.state.read_dict_keys(node):
+            attrs = self.state.create_node()
+            self.state.create_dict(node, Himesis.Constants.ATTRIBUTES, attrs)
+        else:
+            attrs = self.follow_edge(node, Himesis.Constants.ATTRIBUTES)
+        if key in self.state.read_dict_keys(attrs):
+            target = self.follow_edge(attrs, key)
+            self.state.delete_node(target)
+        target = self.state.create_nodevalue(value)
+        self.state.create_dict(attrs, key, target)
+
+    def node_get_attribute(self, node, key):
+        if Himesis.Constants.ATTRIBUTES not in self.state.read_dict_keys(node):
+            return None
+        attrs = self.follow_edge(node, Himesis.Constants.ATTRIBUTES)
+        if key in self.state.read_dict_keys(attrs):
+            target = self.follow_edge(attrs, key)
+            return self.state.read_value(target)
+        return None
+
+    def node_get_attributes(self, node):
+        if Himesis.Constants.ATTRIBUTES not in self.state.read_dict_keys(node):
+            return []
+        attrs = self.follow_edge(node, Himesis.Constants.ATTRIBUTES)
+        res = {}
+        for key in self.state.read_dict_keys(attrs):
+            target = self.follow_edge(attrs, key)
+            res[key] = self.state.read_value(target)
+        return res
+
+    def node_has_attribute(self, node, key):
+        attr = self.node_get_attribute(node, key)
+        return attr is not None
+
+    def get_first_node_with_attribute_equals(self, key, value):
+        for node in self.node_guid_iter():
+            attr = self.node_get_attribute(node, key)
+            if attr == value:
+                return node
+        return None
+
+    def add_node(self, fulltype=None, isConnector=None):
+        # ALL NODES ARE NOW IDENTIFIED WITH A UUID!
+
+        # newNodeIndex = self.vertex_count()
+        # if newNodeGuid is None:
+        #     newNodeGuid = uuid.uuid4()
+        nid = self.state.create_node()
+        # self.node_set_attribute(nid, Himesis.Constants.GUID, newNodeGuid)
+        self.node_set_attribute(nid, Himesis.Constants.FULLTYPE, fulltype)
+        self.node_set_attribute(nid, Himesis.Constants.CONNECTOR_TYPE, isConnector)
+        # self.add_vertices(1)
+        # self.vs[newNodeIndex][Himesis.Constants.GUID] = newNodeGuid
+        # self.vs[newNodeIndex][Himesis.Constants.FULLTYPE] = fulltype
+        # self.vs[newNodeIndex][Himesis.Constants.CONNECTOR_TYPE] = isConnector
         if fulltype in self.mmTypeData :
-            for attr,val in self.mmTypeData[fulltype].items():
-                self.vs[newNodeIndex][str(attr)] = val
-        self._guid2index[newNodeGuid] = newNodeIndex
-        return newNodeIndex
+            for attr, val in self.mmTypeData[fulltype].items():
+                self.node_set_attribute(nid, str(attr), val)
+                # self.vs[newNodeIndex][str(attr)] = val
+        self._index2guid[self.vcount] = nid
+        self.vcount += 1
+        return nid
 
     def delete_nodes(self, nodes):
-        self.delete_vertices(nodes)
+        td = []
+        for n in nodes:
+            self.state.delete_node(n)
+            td += [k for k, v in self._index2guid.items() if v == n]
+        # self.delete_vertices(nodes)
         # Regenerate the lookup because node indices have changed
-        self._guid2index = dict((self.vs[node][Himesis.Constants.GUID], node) for node in self.node_iter())
+        for t in td:
+            del self._index2guid[t]
+        # self._guid2index = dict((self.vs[node][Himesis.Constants.GUID], node) for node in self.node_iter())
 
-    def get_node(self,guid):
-        """
-            Retrieves the node instance with the specified guid
-            @param guid: The guid of the node.
-        """
-        if guid in self._guid2index:
-            if self._guid2index[guid] >= self.vcount() or \
-                    self.vs[self._guid2index[guid]][Himesis.Constants.GUID] != guid :
-                self._guid2index = dict((self.vs[node][Himesis.Constants.GUID], node) for node in self.node_iter())
-            try:
-                return self._guid2index[guid]
-            except KeyError:
-                #TODO: This should be a TransformationLanguageSpecificException
-                raise KeyError('Invalid node id. Make sure to only delete nodes via Himesis.delete_nodes(): ' + str(guid))
-        else :
-            #TODO: This should be a TransformationLanguageSpecificException
-            raise KeyError('Node not found with specified id. Make sure to only create nodes via Himesis.add_node(): ' + str(guid))
-
-    def draw(self, visual_style={}, label=None, show_guid=False, show_id=False, debug=False, width=600, height=900):
-        """
-        Visual graphic rendering of the graph.
-        @param label: The attribute to use as node label in the figure.
-                      If not provided, the index of the node is used.
-        @param visual_style: More drawing options
-        (see http://igraph.sourceforge.net/doc/python/igraph.Graph-class.html#__plot__ for more details).
-        """
-        if 'layout' not in visual_style:
-            visual_style["layout"] = 'fr'
-        if 'margin' not in visual_style:
-            visual_style["margin"] = 10
-
-        # Set the labels
-        if not label:
-            if show_guid:
-                visual_style["vertex_label"] = [str(self.vs[i][Himesis.Constants.GUID])[:4] for i in self.node_iter()]
-            elif show_id:
-                visual_style["vertex_label"] = [str(i) for i in self.node_iter()]
-            else:
-                visual_style["vertex_label"] = [''] *  self.vcount()
+    def get_node(self, index):
+        if index in self._index2guid:
+            return self._index2guid[index]
+        raise KeyError('Node not found with specified id. Make sure to only create nodes via Himesis.add_node().')
+
+    def edge_get_source(self, edge):
+        return self.state.read_edge(edge)[0]
+
+    def edge_get_target(self, edge):
+        return self.state.read_edge(edge)[1]
+
+    def link_nodes(self, A, B, label=None):
+        if label is None:
+            self.state.create_edge(A, B)
         else:
-            try:
-                visual_style["vertex_label"] = self.vs[label]
-                for n in self.node_iter():
-                    if not visual_style["vertex_label"][n]:
-                        visual_style["vertex_label"][n] = self.vs[n][Himesis.Constants.FULLTYPE]
-                        if debug:
-                            visual_style["vertex_label"][n] = str(n) + ':' + visual_style["vertex_label"][n]
-                    elif debug:
-                        visual_style["vertex_label"][n] = str(n) + ':' + visual_style["vertex_label"][n]
-            except:
-                raise Exception('%s is not a valid attribute' % label)
-
-        return ig.plot(self, bbox=(0, 0, width, height), **visual_style)
+            self.state.create_dict(A, label, B)
+
+    def unlink_nodes(self, A, B, label=""):
+        if self.state.read_reverse_dict(B, label) != A:
+            raise ValueError("No connection with label '%s' found between nodes %s and %s" % (label, A, B))
+        edge = self.state.read_dict_edge(A, label)
+        self.state.delete_edge(edge)
+
+    # def get_node(self, guid):
+    #     """
+    #         Retrieves the node instance with the specified guid
+    #         @param guid: The guid of the node.
+    #     """
+    #     if guid in self._guid2index:
+    #         if self._guid2index[guid] >= self.vcount() or \
+    #                 self.vs[self._guid2index[guid]][Himesis.Constants.GUID] != guid :
+    #             self._guid2index = dict((self.vs[node][Himesis.Constants.GUID], node) for node in self.node_iter())
+    #         try:
+    #             return self._guid2index[guid]
+    #         except KeyError:
+    #             #TODO: This should be a TransformationLanguageSpecificException
+    #             raise KeyError('Invalid node id. Make sure to only delete nodes via Himesis.delete_nodes(): ' + str(guid))
+    #     else :
+    #         #TODO: This should be a TransformationLanguageSpecificException
+    #         raise KeyError('Node not found with specified id. Make sure to only create nodes via Himesis.add_node(): ' + str(guid))
+
+    # def draw(self, visual_style={}, label=None, show_guid=False, show_id=False, debug=False, width=600, height=900):
+    #     """
+    #     Visual graphic rendering of the graph.
+    #
+    #     Args:
+    #     @param label: The attribute to use as node label in the figure.
+    #                   If not provided, the index of the node is used.
+    #     @param visual_style: More drawing options
+    #     (see http://igraph.sourceforge.net/doc/python/igraph.Graph-class.html#__plot__ for more details).
+    #     """
+    #     if 'layout' not in visual_style:
+    #         visual_style["layout"] = 'fr'
+    #     if 'margin' not in visual_style:
+    #         visual_style["margin"] = 10
+    #
+    #     # Set the labels
+    #     if not label:
+    #         if show_guid:
+    #             visual_style["vertex_label"] = [str(self.vs[i][Himesis.Constants.GUID])[:4] for i in self.node_iter()]
+    #         elif show_id:
+    #             visual_style["vertex_label"] = [str(i) for i in self.node_iter()]
+    #         else:
+    #             visual_style["vertex_label"] = [''] *  self.vcount()
+    #     else:
+    #         try:
+    #             visual_style["vertex_label"] = self.vs[label]
+    #             for n in self.node_iter():
+    #                 if not visual_style["vertex_label"][n]:
+    #                     visual_style["vertex_label"][n] = self.vs[n][Himesis.Constants.FULLTYPE]
+    #                     if debug:
+    #                         visual_style["vertex_label"][n] = str(n) + ':' + visual_style["vertex_label"][n]
+    #                 elif debug:
+    #                     visual_style["vertex_label"][n] = str(n) + ':' + visual_style["vertex_label"][n]
+    #         except:
+    #             raise Exception('%s is not a valid attribute' % label)
+    #
+    #     return ig.plot(self, bbox=(0, 0, width, height), **visual_style)
 
     def execute(self, *args):
         raise AttributeError('This method is not implemented')
 
 
 class HimesisPattern(Himesis):
-    def __init__(self, name='', num_nodes=0, edges=[]):
-        super(HimesisPattern, self).__init__(name, num_nodes, edges)
+    def __init__(self, name, mvs):
+        super(HimesisPattern, self).__init__(name, mvs)
+
+        # Caches
         self.nodes_label = {}
         self.nodes_pivot_out = {}
 
     def get_node_with_label(self, label):
         """
-            Retrieves the index of the node with the specified label.
-            @param label: The label of the node.
+        Retrieves the index of the node with the specified label.
+
+        Args:
+            label: The label of the node.
         """
         if not self.nodes_label:
-            self.nodes_label = dict([(self.vs[i][Himesis.Constants.MT_LABEL], i) for i in self.node_iter()])
+            self.nodes_label = {}
+            self.nodes_label = { self.node_get_attribute(n, Himesis.Constants.MT_LABEL): n \
+                                    for n in self.node_guid_iter() \
+                                    if self.node_has_attribute(n, Himesis.Constants.MT_LABEL) }
+            # self.nodes_label = dict([(self.vs[i][Himesis.Constants.MT_LABEL], i) for i in self.node_iter()])
         if label in self.nodes_label:
             return self.nodes_label[label]
 
     def get_pivot_out(self, pivot):
         """
-            Retrieves the index of the pivot node
-            @param pivot: The label of the pivot.
+        Retrieves the index of the pivot node.
+
+        Args:
+            pivot: The label of the pivot.
         """
-        if not self.nodes_pivot_out and Himesis.Constants.MT_PIVOT_OUT in self.vs.attribute_names():
-            self.nodes_pivot_out = dict([(i, self.vs[i][Himesis.Constants.MT_PIVOT_OUT]) for i in self.node_iter()])
+        if not self.nodes_pivot_out:
+            self.nodes_pivot_out = { self.node_get_attribute(n, Himesis.Constants.MT_PIVOT_OUT): n \
+                                        for n in self.node_guid_iter() \
+                                        if self.node_has_attribute(n, Himesis.Constants.MT_PIVOT_OUT) }
+            # self.nodes_pivot_out = dict([(i, self.vs[i][Himesis.Constants.MT_PIVOT_OUT]) for i in self.node_iter()])
         if pivot in self.nodes_pivot_out:
             return self.nodes_pivot_out[pivot]
 
 
 class HimesisPreConditionPattern(HimesisPattern):
-    def __init__(self, name='', num_nodes=0, edges=[]):
-        super(HimesisPreConditionPattern, self).__init__(name, num_nodes, edges)
+    def __init__(self, name, mvs):
+        super(HimesisPreConditionPattern, self).__init__(name, mvs)
         self.nodes_pivot_in = {}
 
     def get_pivot_in(self, pivot):
         """
-            Retrieves the index of the pivot node
-            @param pivot: The label of the pivot.
+        Retrieves the index of the pivot node.
+
+        Args:
+            pivot: The label of the pivot.
         """
-        if not self.nodes_pivot_in and Himesis.Constants.MT_PIVOT_IN in self.vs.attribute_names():
-            self.nodes_pivot_in = dict([(self.vs[i][Himesis.Constants.MT_PIVOT_IN], i) for i in self.node_iter()])
+        if not self.nodes_pivot_in:
+            self.nodes_pivot_in = { self.node_get_attribute(n, Himesis.Constants.MT_PIVOT_IN): n \
+                                    for n in self.node_guid_iter() \
+                                    if self.node_has_attribute(n, Himesis.Constants.MT_PIVOT_IN) }
+            # self.nodes_pivot_in = dict([(self.vs[i][Himesis.Constants.MT_PIVOT_IN], i) for i in self.node_iter()])
         if pivot in self.nodes_pivot_in:
             return self.nodes_pivot_in[pivot]
 
-    def constraint(self, mtLabel2graphIndexMap, graph):
+    def constraint(self, mtLabel2graphIndexMap, graph): # TODO??
         """
             If a constraint shall be specified, the corresponding Himesis graph must override this method.
             The condition must be specified in the pattern graph and not the input graph.
@@ -228,14 +357,14 @@ class HimesisPreConditionPattern(HimesisPattern):
 
 
 class HimesisPreConditionPatternLHS(HimesisPreConditionPattern):
-    def __init__(self, name='', num_nodes=0, edges=[]):
-        super(HimesisPreConditionPatternLHS, self).__init__(name, num_nodes, edges)
+    def __init__(self, name, mvs):
+        super(HimesisPreConditionPatternLHS, self).__init__(name, mvs)
         self.NACs = []
         self.bound_start_index = 0  # index of first bound NAC in NACs list
 
     def addNAC(self, nac):
         """
-            Appends the NAC to this LHS pattern
+        Appends the NAC to this LHS pattern
         """
         if nac.LHS != self:
             nac.LHS = self
@@ -245,21 +374,21 @@ class HimesisPreConditionPatternLHS(HimesisPreConditionPattern):
 
     def addNACs(self, NACs):
         """
-            Stores the list of NACs in decreasing order of their size
-            @param nacs: list of NACs
-            @postcondition: the NACs will be stored in decreasing order of their bridge sizes
+        Stores the list of NACs in decreasing order of their size
+        @param nacs: list of NACs
+        @postcondition: the NACs will be stored in decreasing order of their bridge sizes
         """
         bound = []
         unbound = []
         for nac in NACs:
             nac.LHS = self
-            nac.bridge_size = nac.compute_bridge().vcount()
+            nac.bridge_size = nac.compute_bridge().vertex_count()
             if nac.bridge_size > 0:
                 bound.append(nac)
             else:
                 unbound.append(nac)
-        bound.sort(key=lambda nac: (nac.bridge_size, nac.vcount()), reverse=True)
-        unbound.sort(key=lambda nac: nac.vcount(), reverse=True)
+        bound.sort(key=lambda nac: (nac.bridge_size, nac.vertex_count()), reverse=True)
+        unbound.sort(key=lambda nac: nac.vertex_count(), reverse=True)
         self.NACs = unbound + bound
         self.bound_start_index = len(unbound)
 
@@ -274,122 +403,165 @@ class HimesisPreConditionPatternLHS(HimesisPreConditionPattern):
 
 
 class HimesisPreConditionPatternNAC(HimesisPreConditionPattern):
-    def __init__(self, LHS=None, name='', num_nodes=0, edges=[]):
-        super(HimesisPreConditionPatternNAC, self).__init__(name, num_nodes, edges)
+    def __init__(self, LHS, name, mvs):
+        super(HimesisPreConditionPatternNAC, self).__init__(name, mvs)
         self.LHS = LHS
         self.bridge_size = 0
 
     def set_bridge_size(self):
         """
-            Computes the bridge and stores the number of its nodes.
+        Computes the bridge and stores the number of its nodes.
         """
         if self.LHS is None:
             raise Exception('Missing LHS to compute bridge')
-        self.bridge_size = self.compute_bridge().vcount()
+        self.bridge_size = self.compute_bridge().vertex_count()
 
     def compute_bridge(self):
         """
-            Creates a HimesisPreConditionPattern defined as the intersection of graph with this instance.
-            This is called the 'bridge'.
-            From a topological point of view, this method computes the largest common subgraph of these two graphs.
-            However, the similarity of nodes of the bridge relies on the meta-model type of the nodes. 
-            Furthermore, every attribute value is the conjunction of the constraints defined in each graph.
+        Creates a HimesisPreConditionPattern defined as the intersection of graph with this instance.
+        This is called the 'bridge'.
+        From a topological point of view, this method computes the largest common subgraph of these two graphs.
+        However, the similarity of nodes of the bridge relies on the meta-model type of the nodes.
+        Furthermore, every attribute value is the conjunction of the constraints defined in each graph.
         """
         # G1 is the smallest graph and G2 is the bigger graph
         G1 = self
         G2 = self.LHS
-        if G1.vcount() > G2.vcount():
+        if G1.vertex_count() > G2.vertex_count():
             # Swap
             G1, G2 = G2, G1
         # The bridge
-        G = HimesisPreConditionPattern()
+        G = HimesisPreConditionPattern('', self.state)
         G[Himesis.Constants.GUID] = uuid.uuid4()
 
         # We don't need to actually solve the largest common subgraph (LCS) problem
         # because we assume that the nodes are labelled uniquely in each graph
         # and that if a label is in G1 and in G2, then it will be in G
-        if len(G1.vs) == 0:
+        if G1.vertex_count() == 0:
             return G
 
-        Labels2 = G2.vs[Himesis.Constants.MT_LABEL]
-        for label in G1.vs[Himesis.Constants.MT_LABEL]:
-            if label in Labels2:
-                # Get the corresponding node from G1 
-                v1 = G1.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == label)
-                if len(v1) == 1:
-                    v1 = v1[0]
-                elif len(v1) == 0:
-                    #unreachable line...
-                    raise Exception('Label does not exist :: ' + str(label))
+        for v1 in G1.node_guid_iter():
+            label = G1.node_get_attribute(v1, Himesis.Constants.MT_LABEL)
+            v2 = G2.get_first_node_with_attribute_equals(Himesis.Constants.MT_LABEL, label)
+            if v2 is None: continue
+            newNodeIndex = G.add_node()
+            # Now do a conjunction of the attributes
+            for attr, val in G1.node_get_attributes(v1).items():
+                G.node_set_attribute(newNodeIndex, attr, val)
+            for attr, val in G2.node_get_attributes(v2).items():
+                if not G.node_has_attribute(newNodeIndex, attr):
+                    G.node_set_attribute(newNodeIndex, attr, val)
+                # Ignore non-RAM attributes ('special' and HConstants attributes)
+                elif not Himesis.is_RAM_attribute(attr): continue
+                # Handle normal attribute
                 else:
-                    raise Exception('Label is not unique :: ' + str(label))
-                # Get the corresponding node from G2
-                v2 = G2.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == label)
-                if len(v2) == 1:
-                    v2 = v2[0]
-                elif len(v2) == 0:
-                    # Unreachable line...
-                    raise Exception('Label does not exist :: ' + str(label))
-                else:
-                    raise Exception('Label is not unique :: ' + str(label))
-                newNodeIndex = G.add_node()
-                # Now do a conjunction of the attributes
-                for attr in v1.attribute_names():
-                    G.vs[newNodeIndex][attr] = v1[attr]
-                for attr in v2.attribute_names():
-                    # The attribute is not in v1
-                    if attr not in G.vs[newNodeIndex].attribute_names():
-                        G.vs[newNodeIndex][attr] = v2[attr]
-                    # Give this node its own GUID attribute
-                    elif attr == Himesis.Constants.GUID:
-                        G.vs[newNodeIndex][Himesis.Constants.GUID] = uuid.uuid4()
-                        continue
-                    # Ignore non-RAM attributes ('special' and HConstants attributes)
-                    elif not Himesis.is_RAM_attribute(attr):
+                    if not val:
+                        # There is no constraint for this attribute
                         continue
-                    # Handle normal attribute
-                    else :
-                        if not v2[attr]:
-                            # There is no constraint for this attribute
-                            continue
-
-                        # The attribute constraint code is the conjunction of the LHS constraint
-                        # with the NAC constraint for this attribute
-                        def get_evalAttrConditions(_attr,_v1,_v2) :
-                            def evalAttrConditions(mtLabel2graphIndexMap,graph):
-                                return G1.vs[_v1][_attr](mtLabel2graphIndexMap, graph) and \
-                                       G2.vs[_v2][_attr](mtLabel2graphIndexMap, graph)
-                            return evalAttrConditions
-                        G.vs[newNodeIndex][attr] = get_evalAttrConditions(attr,v1.index,v2.index)
-                    #else: v1[attr] == v2[attr], so we don't need to do anything more 
+
+                    # The attribute constraint code is the conjunction of the LHS constraint
+                    # with the NAC constraint for this attribute
+                    def get_evalAttrConditions(_attr, _v1, _v2):
+                        def evalAttrConditions(mtLabel2graphIndexMap, graph):
+                            return G1.node_get_attribute(_v1, _attr)(mtLabel2graphIndexMap, graph) and \
+                                   G2.node_get_attribute(_v2, _attr)(mtLabel2graphIndexMap, graph)
+
+                        return evalAttrConditions
+
+                    G.node_set_attribute(newNodeIndex, attr, get_evalAttrConditions(attr, v1, v2))
+                    # G.vs[newNodeIndex][attr] = get_evalAttrConditions(attr, v1.index, v2.index)
+
         # Now add the edges
         # We only need to go through the edges of the smaller graph
         for e in G1.edge_iter():
-            src_label = G1.vs[G1.es[e].source][Himesis.Constants.MT_LABEL]
-            tgt_label = G1.vs[G1.es[e].target][Himesis.Constants.MT_LABEL]
-            src = G.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == src_label)
-            tgt = G.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == tgt_label)
-            if len(src) == len(tgt) == 1:
-                src = src[0]
-                tgt = tgt[0]
-                G.add_edges([(src.index, tgt.index)])
-            elif len(src) == 0 :
-                #                raise Exception('Label does not exist :: '+str(src_label))
-                pass
-            elif len(tgt) == 0 :
-                #                raise Exception('Label does not exist :: '+str(tgt_label))
-                pass
-            elif len(src) > 1 :
-                raise Exception('Label is not unique :: ' + str(src_label))
-            elif len(tgt) > 1 :
-                raise Exception('Label is not unique :: ' + str(tgt_label))
+            src, tgt = self.state.read_edge(e)
+            src_label = self.node_get_attribute(src, Himesis.Constants.MT_LABEL)
+            tgt_label = self.node_get_attribute(tgt, Himesis.Constants.MT_LABEL)
+            src = G.get_first_node_with_attribute_equals(Himesis.Constants.MT_LABEL, src_label)
+            tgt = G.get_first_node_with_attribute_equals(Himesis.Constants.MT_LABEL, tgt_label)
+            G.link_nodes(src, tgt)
+
+
+
+        # Labels2 = G2.vs[Himesis.Constants.MT_LABEL]
+        # for label in G1.vs[Himesis.Constants.MT_LABEL]:
+        #     if label in Labels2:
+        #         # Get the corresponding node from G1
+        #         v1 = G1.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == label)
+        #         if len(v1) == 1:
+        #             v1 = v1[0]
+        #         elif len(v1) == 0:
+        #             #unreachable line...
+        #             raise Exception('Label does not exist :: ' + str(label))
+        #         else:
+        #             raise Exception('Label is not unique :: ' + str(label))
+        #         # Get the corresponding node from G2
+        #         v2 = G2.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == label)
+        #         if len(v2) == 1:
+        #             v2 = v2[0]
+        #         elif len(v2) == 0:
+        #             # Unreachable line...
+        #             raise Exception('Label does not exist :: ' + str(label))
+        #         else:
+        #             raise Exception('Label is not unique :: ' + str(label))
+        #         newNodeIndex = G.add_node()
+        #         # Now do a conjunction of the attributes
+        #         for attr in v1.attribute_names():
+        #             G.vs[newNodeIndex][attr] = v1[attr]
+        #         for attr in v2.attribute_names():
+        #             # The attribute is not in v1
+        #             if attr not in G.vs[newNodeIndex].attribute_names():
+        #                 G.vs[newNodeIndex][attr] = v2[attr]
+        #             # Give this node its own GUID attribute
+        #             elif attr == Himesis.Constants.GUID:
+        #                 G.vs[newNodeIndex][Himesis.Constants.GUID] = uuid.uuid4()
+        #                 continue
+        #             # Ignore non-RAM attributes ('special' and HConstants attributes)
+        #             elif not Himesis.is_RAM_attribute(attr):
+        #                 continue
+        #             # Handle normal attribute
+        #             else :
+        #                 if not v2[attr]:
+        #                     # There is no constraint for this attribute
+        #                     continue
+        #
+        #                 # The attribute constraint code is the conjunction of the LHS constraint
+        #                 # with the NAC constraint for this attribute
+        #                 def get_evalAttrConditions(_attr,_v1,_v2) :
+        #                     def evalAttrConditions(mtLabel2graphIndexMap,graph):
+        #                         return G1.vs[_v1][_attr](mtLabel2graphIndexMap, graph) and \
+        #                                G2.vs[_v2][_attr](mtLabel2graphIndexMap, graph)
+        #                     return evalAttrConditions
+        #                 G.vs[newNodeIndex][attr] = get_evalAttrConditions(attr,v1.index,v2.index)
+        #             #else: v1[attr] == v2[attr], so we don't need to do anything more
+        # # Now add the edges
+        # # We only need to go through the edges of the smaller graph
+        # for e in G1.edge_iter():
+        #     src_label = G1.vs[G1.es[e].source][Himesis.Constants.MT_LABEL]
+        #     tgt_label = G1.vs[G1.es[e].target][Himesis.Constants.MT_LABEL]
+        #     src = G.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == src_label)
+        #     tgt = G.vs.select(lambda v : v[Himesis.Constants.MT_LABEL] == tgt_label)
+        #     if len(src) == len(tgt) == 1:
+        #         src = src[0]
+        #         tgt = tgt[0]
+        #         G.add_edges([(src.index, tgt.index)])
+        #     elif len(src) == 0 :
+        #         #                raise Exception('Label does not exist :: '+str(src_label))
+        #         pass
+        #     elif len(tgt) == 0 :
+        #         #                raise Exception('Label does not exist :: '+str(tgt_label))
+        #         pass
+        #     elif len(src) > 1 :
+        #         raise Exception('Label is not unique :: ' + str(src_label))
+        #     elif len(tgt) > 1 :
+        #         raise Exception('Label is not unique :: ' + str(tgt_label))
         return G
 
 
 
 class HimesisPostConditionPattern(HimesisPattern):
-    def __init__(self, name='', num_nodes=0, edges=[]):
-        super(HimesisPostConditionPattern, self).__init__(name, num_nodes, edges)
+    def __init__(self, name, mvs):
+        super(HimesisPostConditionPattern, self).__init__(name, mvs)
         self.pre = None
 
     def action(self, mtLabel2graphIndexMap, graph):
@@ -436,23 +608,27 @@ class HimesisPostConditionPattern(HimesisPattern):
         # Set the attributes of graph.vs[graphNodeIndex] to match those of self.vs[rhsNodeIndex]
         def set_attributes(rhsNodeIndex, graphNodeIndex, newNode, pLabel2graphIndexMap) :
             changedSomething = False
-            for attrName in self.vs[rhsNodeIndex].attribute_names() :
+            for attrName, attrVal in self.node_get_attributes(rhsNodeIndex).items():
+            # for attrName in self.vs[rhsNodeIndex].attribute_names() :
                 if Himesis.is_RAM_attribute(attrName) :
-                    attrVal = self.vs[rhsNodeIndex][attrName]
-                    if attrVal == None :
+                    # attrVal = self.vs[rhsNodeIndex][attrName]
+                    if attrVal is None :
                         # Not 'really' an attribute
                         continue
                     oldVal = None
                     try :
-                        if not newNode :
-                            oldVal = graph.vs[graphNodeIndex][attrName]
-
-                        newVal = self.vs[rhsNodeIndex][attrName](pLabel2graphIndexMap, graph)
-                        if oldVal != newVal :
-                            graph.vs[graphNodeIndex][attrName] = newVal
+                        if not newNode:
+                            # oldVal = graph.vs[graphNodeIndex][attrName]
+                            oldVal = graph.node_get_attribute(graphNodeIndex, attrName)
+
+                        # newVal = self.vs[rhsNodeIndex][attrName](pLabel2graphIndexMap, graph)
+                        newVal = self.node_get_attribute(rhsNodeIndex, attrName)(pLabel2graphIndexMap, graph)
+                        if oldVal != newVal:
+                            graph.node_set_attribute(graphNodeIndex, attrName, newVal)
+                            # graph.vs[graphNodeIndex][attrName] = newVal
                             packet.deltas.append(
                                 {'op':'CHATTR',
-                                 'guid':graph.vs[graphNodeIndex][Himesis.Constants.GUID],
+                                 'guid':graphNodeIndex,
                                  'attr':attrName,
                                  'old_val':oldVal,
                                  'new_val':newVal})
@@ -472,17 +648,22 @@ class HimesisPostConditionPattern(HimesisPattern):
             rhsNodeIndex = self.get_node_with_label(label)
             if rhsNodeIndex is None:
                 continue        # not in the interface graph (LHS n RHS)
-            if set_attributes(rhsNodeIndex, labels[label], False, labels) :
-                graph.vs[labels[label]][Himesis.Constants.MT_DIRTY] = True
+            if set_attributes(rhsNodeIndex, labels[label], False, labels):
+                graph.node_set_attribute(labels[label], Himesis.Constants.MT_DIRTY, True)
+                # graph.vs[labels[label]][Himesis.Constants.MT_DIRTY] = True
 
         # Create new nodes (non-connectors first)
-        if self.vcount() == 0 :
+        if self.vertex_count() == 0 :
+            RHS_labels = []
+        else:
             RHS_labels = []
-        else :
-            RHS_labels = self.vs[Himesis.Constants.MT_LABEL]
+            for node in self.node_guid_iter():
+                RHS_labels.append(self.node_get_attribute(node, Himesis.Constants.MT_LABEL))
+            # RHS_labels = self.vs[Himesis.Constants.MT_LABEL]
             # sort non-connectors first
-            RHS_labels.sort(key=lambda x: self.vs[ self.get_node_with_label(x) ][Himesis.Constants.CONNECTOR_TYPE] or False)
-            neighborhood = [graph.vs[labels[l]].attributes() for l in LHS_labels]
+            RHS_labels.sort(key=lambda x: self.node_get_attribute(self.get_node_with_label(x), Himesis.Constants.CONNECTOR_TYPE) or False)
+            # RHS_labels.sort(key=lambda x: self.vs[ self.get_node_with_label(x) ][Himesis.Constants.CONNECTOR_TYPE] or False)
+            neighborhood = [graph.node_get_attributes(labels[l]) for l in LHS_labels]
 
         new_labels = []
         for label in RHS_labels:
@@ -490,37 +671,38 @@ class HimesisPostConditionPattern(HimesisPattern):
             if label not in LHS_labels:
                 new_labels += [label]
                 newNodeIndex = graph.add_node(
-                    self.vs[rhsNodeIndex][Himesis.Constants.FULLTYPE],
-                    self.vs[rhsNodeIndex][Himesis.Constants.CONNECTOR_TYPE])
+                    self.node_get_attribute(rhsNodeIndex, Himesis.Constants.FULLTYPE),
+                    self.node_get_attribute(rhsNodeIndex, Himesis.Constants.CONNECTOR_TYPE))
                 packet.deltas.append(
                     {'op':'MKNODE',
                      'neighborhood':neighborhood,
-                     'guid':graph.vs[newNodeIndex][Himesis.Constants.GUID]})
+                     'guid':graph.node_get_attribute(newNodeIndex, Himesis.Constants.GUID)})
                 labels[label] = newNodeIndex
                 set_attributes(rhsNodeIndex, newNodeIndex, True, labels)
 
         # Link new nodes (Create new edges)
         visited_edges = []
         for label in sorted(new_labels):
-            for edge in self.es.select(lambda e: (e.index not in visited_edges and
-                                                  (label == self.vs[e.source][Himesis.Constants.MT_LABEL] or
-                                                   label == self.vs[e.target][Himesis.Constants.MT_LABEL]))):
-                src_label = self.vs[edge.source][Himesis.Constants.MT_LABEL]
-                tgt_label = self.vs[edge.target][Himesis.Constants.MT_LABEL]
-                graph.add_edges([(labels[src_label], labels[tgt_label])])
+            edges = [e for e in self.edge_iter() if (e not in visited_edges and
+                                                     (label == self.node_get_attribute(self.edge_get_source(e), Himesis.Constants.MT_LABEL) or
+                                                      label == self.node_get_attribute(self.edge_get_target(e), Himesis.Constants.MT_LABEL)))]
+            for edge in edges:
+                # src_label = self.node_get_attribute(self.edge_get_source(edge), Himesis.Constants.MT_LABEL)
+                # tgt_label = self.node_get_attribute(self.edge_get_target(edge), Himesis.Constants.MT_LABEL)
+                graph.link_nodes(self.edge_get_source(edge), self.edge_get_target(edge))
+                # graph.add_edges([(labels[src_label], labels[tgt_label])])
                 packet.deltas.append(
                     {'op':'MKEDGE',
-                     'guid1':graph.vs[labels[src_label]][Himesis.Constants.GUID],
-                     'guid2':graph.vs[labels[tgt_label]][Himesis.Constants.GUID]})
+                     'guid1':self.edge_get_source(edge),
+                     'guid2':self.edge_get_target(edge)})
                 visited_edges.append(edge.index)
 
         # Set the output pivots
-        if Himesis.Constants.MT_PIVOT_OUT in self.vs.attribute_names():
-            for node in self.vs.select(lambda v: v[Himesis.Constants.MT_PIVOT_OUT]):
-                node = node.index
-                label = self.vs[node][Himesis.Constants.MT_LABEL]
-                pivot_out = self.vs[node][Himesis.Constants.MT_PIVOT_OUT]
-                packet.global_pivots[pivot_out] = graph.vs[labels[label]][Himesis.Constants.GUID]
+        for node in self.node_guid_iter():
+            pivot_out = self.node_get_attribute(node, Himesis.Constants.MT_PIVOT_OUT)
+            if pivot_out:
+                label = self.node_get_attribute(node, Himesis.Constants.MT_LABEL)
+                packet.global_pivots[pivot_out] = labels[label]
 
         # Perform the post-action
         try:
@@ -535,18 +717,20 @@ class HimesisPostConditionPattern(HimesisPattern):
         for label in LHS_labels:
             if label not in RHS_labels:
                 labels_to_delete.append(labels[label])
-                rmnodes.append({'op':'RMNODE','attrs':graph.vs[labels[label]].attributes()})
-                for edge in graph.es.select(lambda e: (labels[label] == e.source or labels[label] == e.target)) :
-                    found = False
+                rmnodes.append({'op':'RMNODE','attrs': graph.node_get_attributes(labels[label]),'guid': labels[label]})
+                edges = [e for e in graph.edge_iter() if labels[label] == graph.edge_get_source(e) or labels[label] == graph.edge_get_target(e)]
+                for edge in edges:
+                # for edge in graph.es.select(lambda e: (labels[label] == e.source or labels[label] == e.target)) :
+                #     found = False
                     for rmedge in rmedges :
-                        if rmedge['guid1'] == graph.vs[edge.source][Himesis.Constants.GUID] and \
-                                rmedge['guid2'] == graph.vs[edge.target][Himesis.Constants.GUID] :
-                            found = True
+                        if rmedge['guid1'] == graph.edge_get_source(edge) and \
+                                rmedge['guid2'] == graph.edge_get_target(edge):
+                            # found = True
                             break
-                    if not found :
+                    else:
                         rmedges.append({'op':'RMEDGE',
-                                        'guid1':graph.vs[edge.source][Himesis.Constants.GUID],
-                                        'guid2':graph.vs[edge.target][Himesis.Constants.GUID]})
+                                        'guid1':graph.edge_get_source(edge),
+                                        'guid2':graph.edge_get_target(edge)})
         if len(labels_to_delete) > 0 :
             packet.deltas = rmedges + rmnodes + packet.deltas
             graph.delete_nodes(labels_to_delete)
@@ -555,7 +739,7 @@ class HimesisPostConditionPattern(HimesisPattern):
             for uuid in packet.global_pivots:
                 deleted=False
                 for toBeDeleted in rmnodes:
-                    if toBeDeleted['attrs']['$GUID__'] == packet.global_pivots[uuid]:
+                    if toBeDeleted['guid'] == packet.global_pivots[uuid]:
                         del packet.global_pivots[uuid]
                         deleted=True
                         continue

+ 34 - 0
state/__init__.py

@@ -0,0 +1,34 @@
+"""
+Root of the ModelVerse State package
+"""
+
+from enum import Enum
+
+class MvSKernel(Enum):
+	PYTHON  = 0
+	RDF     = 1
+	NEO4J   = 2
+	IGRAPH  = 3
+
+
+def getMvSBackend(type, *args, **kwargs):
+	"""
+	Obtains a state implementation for the ModelVerse State.
+
+	Args:
+		type (MvSKernel):    The type of backend to use.
+
+	All other arguments are implementation-specific.
+	"""
+	if type == MvSKernel.PYTHON:
+		from .pystate import PyState
+		return PyState()
+	elif type == MvSKernel.RDF:
+		from .rdfstate import RDFState
+		return RDFState(*args, **kwargs)
+	elif type == MvSKernel.NEO4J:
+		from .neo4jstate import Neo4jState
+		return Neo4jState(*args, **kwargs)
+	elif type == MvSKernel.IGRAPH:
+		from .igraphstate import iGraphState
+		return iGraphState()

BIN
state/test.png


+ 1 - 1
state/test/fixtures/state.py

@@ -9,7 +9,7 @@ from state.neo4jstate import Neo4jState
     (iGraphState,),
     (PyState,),
     (RDFState, "http://example.org/#"),
-    # (Neo4jState,)
+    (Neo4jState,)
 ])
 def state(request):
     if len(request.param) > 1: