Repository: HLR/DomiKnowS (language: python)

Issue:
Update use relative path for ACE example
Update to ACE example


Discussion / hints:

Fix patch:
diff --git a/docs/NER.png b/docs/NER.png
new file mode 100644
index 000000000..66cbcc1c3
Binary files /dev/null and b/docs/NER.png differ
diff --git a/docs/PIPELINE.md b/docs/PIPELINE.md
index 7b4ba92be..6d4200925 100644
--- a/docs/PIPELINE.md
+++ b/docs/PIPELINE.md
@@ -66,6 +66,9 @@ One can add more complex logical constraints with our logical expression notatio
 
 See [here](developer/KNOWLEDGE.md) for more details about declaring graph and constraints.
 
+The followig figure illustrate the graph for this task:
+![plot](NER.png)
+
 ## 2. Model Declaration
 
 Class reference:
@@ -181,8 +184,7 @@ The extra argument `label=True` indicates it is the ground-true value that this
 `Learner`s, are similar to `Sensor`s. The only difference is that `Learner`s have trainable parameters. The `Program` will update the parameters in `Learner`s based on model performance.
 
 ```python
-token['emb'] = ModuleLearner('ids', module=BERT())
-
+word['emb'] = ModuleLearner('ids', module=BERT())
 word[people] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
 word[organization] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
 pair[wor_for] = ModuleLearner('emb', module=torch.nn.Linear(768*2, 2))
@@ -190,14 +192,14 @@ pair[wor_for] = ModuleLearner('emb', module=torch.nn.Linear(768*2, 2))
 
 The above snippet shows we can assign `Learner`s to `Property`s of `Concept`s.
 `ModuleLearner` is specifically useful to plug in PyTorch modules.
-Specificly, `token['emb']` is instanciated as a `BERT()` module (implemented using the [`transformers` package](https://huggingface.co/transformers/)) which takes `'ids'` of `word` as input.
+Specificly, `word['emb']` is instanciated as a `BERT()` module (implemented using the [`transformers` package](https://huggingface.co/transformers/)) which takes `'ids'` of `word` as input.
 Three other `ModuleLearner`s are assigned to `Property`s named by `people`, `organization`, and `workd_for`, which are sub-types of `word` and `pair`, implemented by PyTorch linear module `torch.nn.Linear`.
 These indicates they are type classifiers of the concept.
 
 It should be noticed that we have assigned `ReaderSensor`s to the same `Property`s of `word` and `pair`.
 This is the ["Multiple Assignment" semantic](MODEL.md#multiple-assigment-convention) of the framework.
 Instead of overwriting the assignment, "Multiple Assignment" indicates consistency of the `Sensor`s and `Learner`s assigned.
-For the aboce example, the framework will generate loss to impose consistency between corresponding `ReaderSensor` and `ModuleLearner`.
+For the above example, the framework will generate loss to impose consistency between corresponding `ReaderSensor` and `ModuleLearner`.
 
 Now in the `graph`, the `Property`s of `Concept`s are assigned with different types of `Sensor`s and `Learner`s.
 We can create a `Program` from the `graph`.
@@ -208,6 +210,10 @@ program = POIProgram(graph, loss=..., metrics=...)
 
 `POIProgram` is a wrapper of training and testing behavior using specific properties in the graph. [Here](./apis/program) is a list of different programs avaliable for the uses. Now, `program` is a "full program" with data and modeling behavior attached.
 It can be used for training, testing, etc. with `Reader`s as input.
+Two other important components of `program` are `loss` and `metrics`. In `loss` you define the loss function by which your model will be updated.
+Using `metrics`, you will measure the performance of your model. For example, you would like to see the *F1-score*, *precision*, *recall*, etc.
+You can find explanation about different `loss` function [here](../regr/program/loss.py) , and
+explanation about different `metrics` [here](../regr/program/metric.py) .
 
 ## 3. Training and Testing
 
diff --git a/examples/ACE05/ace05/annotation.py b/examples/ACE05/ace05/annotation.py
index 51196644b..d8e69b43c 100644
--- a/examples/ACE05/ace05/annotation.py
+++ b/examples/ACE05/ace05/annotation.py
@@ -22,6 +22,9 @@ def from_node(cls, node, *args, **kwargs):
     def parse_node(cls, node):
         return {}
 
+    def apply_offset(self, offset):
+        raise NotImplementedError()
+
 
 class Charseq(APFObject):
     tag = 'charseq'
@@ -45,11 +48,18 @@ def parse_node(cls, node, text):
                     b.splitlines(keepends=True))))
         return node_args
 
-    def __init__(self, start, end, text):
+    def __init__(self, start, end, text, offset=0):
         super().__init__()
         self.start = start
         self.end = end
         self.text = text
+        self.offset = offset
+
+    def apply_offset(self, offset):
+        assert self.offset == 0
+        self.start -= offset
+        self.end -= offset
+        self.offset = offset
 
 
 class Span(APFObject):
@@ -74,6 +84,9 @@ def __init__(self, id, extent, head, span_basetype, span_type, span_subtype):
             self.span_basetype = span_basetype
             self.span_type = span_type
             self.span_subtype = span_subtype
+        
+        def apply_offset(self, offset):
+            self.extent.apply_offset(offset)  # head is a reference to extent
 
     @classmethod
     def from_node(cls, node, *args, **kwargs):
@@ -115,11 +128,15 @@ def __init__(self, id, basetype, type, subtype, mentions):
         self.subtype = subtype
         self.mentions = mentions
 
-    def __copy__(self):
-        return type(self)(id=self.id, basetype=self.basetype, type=self.type, subtype=self.subtype, mentions=self.mentions)
+    def apply_offset(self, offset):
+        for mention in self.mentions.values():
+            mention.apply_offset(offset)
+
+    # def __copy__(self):
+    #     return type(self)(id=self.id, basetype=self.basetype, type=self.type, subtype=self.subtype, mentions=self.mentions)
 
-    def __deepcopy__(self, memo):
-        return type(self)(id=self.id, basetype=self.basetype, type=self.type, subtype=self.subtype, mentions=dict(self.mentions))
+    # def __deepcopy__(self, memo):
+    #     return type(self)(id=self.id, basetype=self.basetype, type=self.type, subtype=self.subtype, mentions=dict(self.mentions))
 
 
 class Entity(Span):
@@ -139,6 +156,10 @@ def __init__(self, id, extent, head, span_basetype, span_type, span_subtype, typ
             super().__init__(id, extent, head, span_basetype, span_type, span_subtype)
             self.type = type
 
+        def apply_offset(self, offset):
+            super().apply_offset(offset)
+            self.head.apply_offset(offset)
+
     class Attribute(APFObject):
         tag = 'name'
 
@@ -154,11 +175,19 @@ def __init__(self, name, text):
             self.name = name
             self.text = text
 
+        def apply_offset(self, offset):
+            self.text.apply_offset(offset)
+
     def __init__(self, id, basetype, type, subtype, mentions, entity_class, attributes):
         super().__init__(id, basetype, type, subtype, mentions)
         self.entity_class = entity_class
         self.attributes = attributes
 
+    def apply_offset(self, offset):
+        super().apply_offset(offset)
+        for attribute in self.attributes:
+            attribute.apply_offset(offset)
+
     @classmethod
     def parse_node(cls, node, text):
         node_args = super().parse_node(node, text)
@@ -176,11 +205,11 @@ def init_types(cls, node, text):
         subtype = ace05['Entities']['{}-{}'.format(node.attrib['TYPE'], node.attrib['SUBTYPE'])]
         return basetype, type, subtype
 
-    def __copy__(self):
-        return type(self)(id=self.id, basetype=self.basetype, type=self.type, subtype=self.subtype, mentions=self.mentions, entity_class=self.entity_class, attributes=self.attributes)
+    # def __copy__(self):
+    #     return type(self)(id=self.id, basetype=self.basetype, type=self.type, subtype=self.subtype, mentions=self.mentions, entity_class=self.entity_class, attributes=self.attributes)
 
-    def __deepcopy__(self, memo):
-        return type(self)(id=self.id, basetype=self.basetype, type=self.type, subtype=self.subtype, mentions=dict(self.mentions), entity_class=self.entity_class, attributes=list(self.attributes))
+    # def __deepcopy__(self, memo):
+    #     return type(self)(id=self.id, basetype=self.basetype, type=self.type, subtype=self.subtype, mentions=dict(self.mentions), entity_class=self.entity_class, attributes=list(self.attributes))
 
 
 class Timex2(Span):
@@ -227,6 +256,10 @@ def parse_node(cls, node, text, span):
             node_args['head'] = Charseq.from_node(node.find('anchor/charseq'), text)
             return node_args
 
+        def apply_offset(self, offset):
+            super().apply_offset(offset)
+            self.head.apply_offset(offset)
+
     @classmethod
     def init_types(cls, node, text):
         basetype = cls.__name__.lower()
@@ -255,6 +288,9 @@ def __init__(self, refid, ref, role):
         self.ref = ref
         self.role = role
 
+    def apply_offset(self, offset):
+        pass  # should not update reference `ref`
+
 
 class Relation(APFObject):
     tag = 'relation'
@@ -293,6 +329,14 @@ def __init__(self, id, lexical_condition, extent, arguments, additional_argument
             self.arguments = arguments
             self.additional_arguments = additional_arguments
 
+        def apply_offset(self, offset):
+            self.extent.apply_offset(offset)
+            for argument in self.arguments:
+                if argument:
+                    argument.apply_offset(offset)
+            for argument in self.additional_arguments:
+                argument.apply_offset(offset)
+
     @classmethod
     def parse_node(cls, node, text, spans):
         node_args = super().parse_node(node)
@@ -325,11 +369,20 @@ def __init__(self, id, type, subtype, tense, modality, arguments, additional_arg
         self.additional_arguments = additional_arguments
         self.mentions = mentions
 
-    def __copy__(self):
-        return type(self)(id=self.id, type=self.type, subtype=self.subtype, tense=self.tense, modality=self.modality, arguments=self.arguments, additional_arguments=self.additional_arguments, mentions=self.mentions)
+    def apply_offset(self, offset):
+        for argument in self.arguments:
+            if argument:
+                argument.apply_offset(offset)
+        for argument in self.additional_arguments:
+            argument.apply_offset(offset)
+        for mention in self.mentions.values():
+            mention.apply_offset(offset)
 
-    def __deepcopy__(self, memo):
-        return type(self)(id=self.id, type=self.type, subtype=self.subtype, tense=self.tense, modality=self.modality, arguments=list(self.arguments), additional_arguments=list(self.additional_arguments), mentions=dict(self.mentions))
+    # def __copy__(self):
+    #     return type(self)(id=self.id, type=self.type, subtype=self.subtype, tense=self.tense, modality=self.modality, arguments=self.arguments, additional_arguments=self.additional_arguments, mentions=self.mentions)
+
+    # def __deepcopy__(self, memo):
+    #     return type(self)(id=self.id, type=self.type, subtype=self.subtype, tense=self.tense, modality=self.modality, arguments=list(self.arguments), additional_arguments=list(self.additional_arguments), mentions=dict(self.mentions))
 
 
 class Event(APFObject):
@@ -375,6 +428,13 @@ def __init__(self, id, extent, ldc_scope, anchor, trigger, arguments):
             self.trigger = trigger
             self.arguments = arguments
 
+        def apply_offset(self, offset):
+            self.extent.apply_offset(offset)
+            self.ldc_scope.apply_offset(offset)
+            self.anchor.apply_offset(offset)
+            for argument in self.arguments:
+                argument.apply_offset(offset)
+
     @classmethod
     def parse_node(cls, node, text, spans):
         node_args = super().parse_node(node)
@@ -412,8 +472,15 @@ def __init__(self, id, type, subtype, trigger, modality, polarity, genericity, t
         self.arguments = arguments
         self.mentions = mentions
 
-    def __copy__(self):
-        return type(self)(id=self.id, type=self.type, subtype=self.subtype, trigger=self.trigger, modality=self.modality, polarity=self.polarity, genericity=self.genericity, tense=self.tense, arguments=self.arguments, mentions=self.mentions)
+    def apply_offset(self, offset):
+        for argument in self.arguments:
+            if argument:
+                argument.apply_offset(offset)
+        for mention in self.mentions.values():
+            mention.apply_offset(offset)
+
+    # def __copy__(self):
+    #     return type(self)(id=self.id, type=self.type, subtype=self.subtype, trigger=self.trigger, modality=self.modality, polarity=self.polarity, genericity=self.genericity, tense=self.tense, arguments=self.arguments, mentions=self.mentions)
 
-    def __deepcopy__(self, memo):
-        return type(self)(id=self.id, type=self.type, subtype=self.subtype, trigger=deepcopy(self.trigger, memo), modality=self.modality, polarity=self.polarity, genericity=self.genericity, tense=self.tense, arguments=list(self.arguments), mentions=dict(self.mentions))
+    # def __deepcopy__(self, memo):
+    #     return type(self)(id=self.id, type=self.type, subtype=self.subtype, trigger=deepcopy(self.trigger, memo), modality=self.modality, polarity=self.polarity, genericity=self.genericity, tense=self.tense, arguments=list(self.arguments), mentions=dict(self.mentions))
diff --git a/examples/ACE05/ace05/graph.py b/examples/ACE05/ace05/graph.py
index 586693e23..c27ef2120 100644
--- a/examples/ACE05/ace05/graph.py
+++ b/examples/ACE05/ace05/graph.py
@@ -1,6 +1,6 @@
 from regr.graph import Graph, Concept, Relation, Property
-from regr.graph.relation import disjoint
-from regr.graph.logicalConstrain import ifL, orL, andL
+# from regr.graph.relation import disjoint
+from regr.graph.logicalConstrain import ifL, nandL, orL, andL
 
 
 Graph.clear()
@@ -14,6 +14,10 @@
         span_candidate = Concept(name='span_candidate')
         span_annotation = Concept(name='span_annotation')
         anchor_annotation = Concept(name='anchor_annotation')
+        token_b = token(name='b')
+        token_i = token(name='i')
+        token_o = token(name='o')
+        nandL(token_b, token_i, token_o)
         span = Concept(name='span')
         document = Concept(name='document')
         span_candidate.has_a(start=token, end=token)
@@ -51,7 +55,7 @@
             usp = entity(name='Class.USP')
 
             # disjoint
-            disjoint(neg, spc, gen, usp)
+            nandL(neg, spc, gen, usp)
 
             # Types
             # Person - Person entities are limited to humans. A person may be a single individual or a group
@@ -84,7 +88,7 @@
             sen = value(name='Sentence')
 
             # disjoint
-            disjoint(person, organization, gpe, location, facility, vehicle, weapon, timex2, value)
+            nandL(person, organization, gpe, location, facility, vehicle, weapon, timex2, value)
 
             # Abbreviation
             PER = person
@@ -103,7 +107,7 @@
             # PER.Indefinite - If from the context you can’t judge whether the Person entity refers to one or more than one person, tag it as PER.Indefinite.
             indefinite = person('PER-Indeterminate')
             # disjoint
-            disjoint(individual, group, indefinite)
+            nandL(individual, group, indefinite)
 
             # organization subtypes
             # Government (GOV) - Government organizations are those that are of, relating to, or dealing with the structure or affairs of government, politics, or the state.
@@ -126,7 +130,7 @@
             sports = organization('ORG-Sports')
             # NOTE: The collection of organization subtypes is hierarchical in nature. ACE05 assigns the most specific type possible. So the annotated results are disjoint.
             # disjoint
-            disjoint(government, commercial, educational, entertainment, non_governmental, media, religious, medical_science, sports)
+            nandL(government, commercial, educational, entertainment, non_governmental, media, religious, medical_science, sports)
 
             # GPE subtypes
             # Continent - Taggable mentions of the entireties of any of the seven continents.
@@ -144,7 +148,7 @@
             # Special - A closed set of GPEs for which the conventional labels do not straightforwardly apply.
             special = GPE('GPE-Special')
             # disjoint
-            disjoint(continent, nation, state_or_province, county_or_district, population_center, gpe_cluster, special)
+            nandL(continent, nation, state_or_province, county_or_district, population_center, gpe_cluster, special)
 
             # GPE Roles - Annotators need to decide for each entity mention in the text which role (Person, Organization, Location, GPE) the context of that mention invokes. This judgment typically depends on the relations that the entity enters into.
             # TODO: Similar to Class, this is a field required from one the the following four.
@@ -159,7 +163,7 @@
             # GPE.GPE - GPE.GPE is used when more than one of the other GPE roles is being referenced at once or when no one role stands out in the context.
             gpe_gpe = gpe('Role.GPE')
             # disjoint
-            disjoint(gpe_org, gpe_per, gpe_loc, gpe_gpe)
+            nandL(gpe_org, gpe_per, gpe_loc, gpe_gpe)
 
             # Location subtypes
             # Address - A location denoted as a point such as in a postal system or abstract coordinates. The name of a location in a postal system is also an address.
@@ -177,7 +181,7 @@
             # Region-General - Taggable locations that do not cross national borders.
             region_general = location('LOC-Region-General')
             # disjoint
-            disjoint(address, boundary, celestial, water_body, land_region_natural, region_international, region_general)
+            nandL(address, boundary, celestial, water_body, land_region_natural, region_international, region_general)
 
             # Facilities subtypes
             # Airport - A facility whose primary use is as an airport.
@@ -191,7 +195,7 @@
             # Path - A facility that allows fluids, energies, persons or vehicles to pass from one location to another.
             path = facility('FAC-Path')
             # disjoint
-            disjoint(airport, plant, building_or_grounds, subarea_facility, path)
+            nandL(airport, plant, building_or_grounds, subarea_facility, path)
 
             # Vehicle subtypes
             # Air - Vehicles designed to locomote primarily through the air, not touching water or land.
diff --git a/examples/ACE05/ace05/reader.py b/examples/ACE05/ace05/reader.py
index a0de70b06..95a42e64e 100644
--- a/examples/ACE05/ace05/reader.py
+++ b/examples/ACE05/ace05/reader.py
@@ -136,8 +136,10 @@ def __iter__(self):
 
 
 class ParagraphReader(Reader):
-    def _filter_span(self, spans, start, end):
+    def _filter_span(self, spans, start, end, offset=None):
         from copy import deepcopy
+        if offset is None:
+            offset = start
         new_spans = {}
         for span_id, span_ in spans.items():
             span = deepcopy(span_)
@@ -146,11 +148,14 @@ def _filter_span(self, spans, start, end):
                     start < mention.extent.end and mention.extent.end < end):
                     del span.mentions[key]
             if span.mentions: # if there is any left
+                span.apply_offset(offset)
                 new_spans[span_id] = span
         return new_spans
 
-    def _filter_relation(self, relations, spans, start, end):
+    def _filter_relation(self, relations, spans, start, end, offset=None):
         from copy import deepcopy
+        if offset is None:
+            offset = start
         new_relations = {}
         for relation_id, relation in relations.items():
             relation = deepcopy(relation)
@@ -159,11 +164,14 @@ def _filter_relation(self, relations, spans, start, end):
                     start < mention.extent.end and mention.extent.end < end):
                     del relation.mentions[key]
             if relation.mentions: # if there is any left
+                relation.apply_offset(offset)
                 new_relations[relation_id] = relation
         return new_relations
 
-    def _filter_event(self, events, spans, start, end):
+    def _filter_event(self, events, spans, start, end, offset=None):
         from copy import deepcopy
+        if offset is None:
+            offset = start
         new_events = {}
         for event_id, event in events.items():
             event = deepcopy(event)
@@ -172,6 +180,7 @@ def _filter_event(self, events, spans, start, end):
                     start < mention.extent.end and mention.extent.end < end):
                     del event.mentions[key]
             if event.mentions: # if there is any left
+                event.apply_offset(offset)
                 new_events[event_id] = event
         return new_events
 
diff --git a/examples/ACE05/config.py b/examples/ACE05/config.py
index 0ac857a75..5ec32cf93 100644
--- a/examples/ACE05/config.py
+++ b/examples/ACE05/config.py
@@ -1,6 +1,6 @@
 from ace05.errors import KNOWN_ERRORS_NONE, KNOWN_ERRORS_ADJ, KNOWN_ERRORS_TIMEX2NORM
 
-path = '/home/hlr/shared/data/LDC2006T06/ace_2005_td_v7'
+path = 'data/ace05_samples'
 list_path = 'data/data_list.csv'
 status = 'timex2norm'  # timex2norm, adj, fp1, fp2
 known_errors = KNOWN_ERRORS_TIMEX2NORM  # KNOWN_ERRORS_NONE, KNOWN_ERRORS_ADJ, KNOWN_ERRORS_TIMEX2NORM
diff --git a/examples/ACE05/main.py b/examples/ACE05/main.py
index bcccce675..5a532e0a3 100644
--- a/examples/ACE05/main.py
+++ b/examples/ACE05/main.py
@@ -6,6 +6,7 @@
 
 from dummy import DummyReader, DummyDictReader
 
+
 def main():
     program = model(graph)
     traint_reader = DictParagraphReader(config.path, list_path=config.list_path, type='train', status=config.status)
@@ -18,5 +19,6 @@ def main():
         span_annotations = node.findDatanodes(select=graph['linguistic/span_annotation'])
         print(len(tokens), len(spans), len(span_annotations))
 
+
 if __name__ == "__main__":
     main()
diff --git a/examples/ACE05/model.py b/examples/ACE05/model.py
index 323e83ea0..9d0a40a3b 100644
--- a/examples/ACE05/model.py
+++ b/examples/ACE05/model.py
@@ -3,11 +3,11 @@
 from regr.program import POIProgram
 from regr.program.primaldualprogram import PrimalDualProgram
 from regr.program.model.pytorch import PoiModel
-from regr.sensor.pytorch.sensors import ReaderSensor, ConstantSensor, FunctionalSensor, FunctionalReaderSensor, cache, TorchCache
+from regr.sensor.pytorch.sensors import ReaderSensor, ConstantSensor, FunctionalSensor, FunctionalReaderSensor, cache, TorchCache, JointSensor
 from regr.sensor.pytorch.tokenizers.transformers import TokenizerEdgeSensor
 from regr.sensor.pytorch.relation_sensors import EdgeSensor
 from regr.sensor.pytorch.learners import ModuleLearner
-from regr.sensor.pytorch.relation_sensors import CandidateSensor, CandidateRelationSensor, CandidateEqualSensor
+from regr.sensor.pytorch.relation_sensors import CandidateSensor, CandidateRelationSensor, CandidateEqualSensor, CompositionCandidateSensor
 
 from sensors.readerSensor import MultiLevelReaderSensor, SpanLabelSensor, CustomMultiLevelReaderSensor, LabelConstantSensor
 from models import Tokenizer, BERT, SpanClassifier, token_to_span_candidate_emb, span_to_pair_emb, find_is_a, find_event_arg, token_to_span_label, makeSpanPairs, makeSpanAnchorPairs
@@ -15,32 +15,17 @@
 
 def model(graph):
     from ace05.graph import graph, entities_graph, events_graph
-    from ace05.graph import document, token, span_candidate, span, span_annotation, anchor_annotation, event, pair
+    from ace05.graph import document, token, token_b, token_i, token_o, span, span_annotation, anchor_annotation, event, pair
 
     # document
-    document['index'] = ReaderSensor(keyword='text')
+    document['text'] = ReaderSensor(keyword='text')
 
     # document -> token
     document_contains_token = document.relate_to(token)[0]
-    token['index', 'offset'] = cache(TokenizerEdgeSensor)('index', mode='forward', relation=document_contains_token, tokenizer=Tokenizer(), cache=TorchCache(path="./cache/tokenizer"))
-    token['emb'] = ModuleLearner('index', module=BERT())
+    token[document_contains_token.forward, 'ids', 'offset'] = cache(JointSensor)(document['text'], forward=Tokenizer(), cache=TorchCache(path="./cache/tokenizer"))
+    token['emb'] = ModuleLearner('ids', module=BERT())
 
-    # token -> span
-    span_candidate['index'] = CandidateSensor(token['index'], forward=lambda *_: True)
-    span_candidate['emb'] = FunctionalSensor(token['emb'], forward=token_to_span_candidate_emb)
-    span_candidate['label'] = ModuleLearner('emb', module=SpanClassifier(token_emb_dim=768))
-
-    span_contains_token = span.relate_to(token)[0]
-    # span_contains_token['backward'] = TorchEdgeSensor(
-    #     span_candidate['label'], to='index', forward=token_to_span_label, mode='backward',)
-    def token_to_span_fn(token_index):
-        token_index
-        pass
-    span['index'] = EdgeSensor('index', mode='backward', relation=span_contains_token, forward=token_to_span_fn)
-
-    span['emb'] = FunctionalSensor(span_candidate['emb'], forward=lambda x: x)
-
-    # span equality extention
+    # span annotation
     span_annotation['index'] = MultiLevelReaderSensor(keyword="spans.*.mentions.*.head.text")
     span_annotation['start'] = MultiLevelReaderSensor(keyword="spans.*.mentions.*.head.start")
     span_annotation['end'] = MultiLevelReaderSensor(keyword="spans.*.mentions.*.head.end")
@@ -53,16 +38,80 @@ def token_to_span_fn(token_index):
     anchor_annotation['type'] = CustomMultiLevelReaderSensor(keyword="events.*.type")
     anchor_annotation['subtype'] = CustomMultiLevelReaderSensor(keyword="events.*.subtype")
 
+    # token -> span and span equality extention
+    span_contains_token = span.relate_to(token)[0]
     span_equal_annotation = span.relate_to(span_annotation)[0]
-    span['match'] = CandidateEqualSensor('index', span_annotation['index'],span_annotation['start'], span_annotation['end'], forward=makeSpanPairs, relations=[span_equal_annotation])
     anchor_equal_annotation = span.relate_to(anchor_annotation)[0]
-    span['match1'] = CandidateEqualSensor('index', anchor_annotation['index'], anchor_annotation['start'], anchor_annotation['end'], forward=makeSpanAnchorPairs, relations=[anchor_equal_annotation])
-    span['label'] = SpanLabelSensor('match', label=True, concept=span_annotation.name)
+
+    def token_to_span_fn(token_offset, sanno_start, sanno_end, aanno_start, aanno_end):
+        num_token = token_offset.shape[0]
+        token_start = token_offset[:,0]
+        token_end = token_offset[:,1]
+        spans = []
+        # dummy spans
+        for start in range(0, num_token, 4):
+            spans.append((start, start+2))
+        sannos = []
+        for start, end in zip(sanno_start, sanno_end):
+            start_token = torch.nonzero(torch.logical_and(token_start <= start, start < token_end), as_tuple=False)[0, 0]
+            end_token = torch.nonzero(torch.logical_and(token_start < end, end <= token_end), as_tuple=False)[0, 0]
+            try:
+                span_index = spans.index((start_token, end_token))
+            except ValueError:
+                span_index = len(spans)
+                spans.append((start_token, end_token))
+            sannos.append(span_index)
+        aannos = []
+        for start, end in zip(aanno_start, aanno_end):
+            start_token = torch.nonzero(torch.logical_and(token_start <= start, start < token_end), as_tuple=False)[0, 0]
+            end_token = torch.nonzero(torch.logical_and(token_start < end, end <= token_end), as_tuple=False)[0, 0]
+            try:
+                span_index = spans.index((start_token, end_token))
+            except ValueError:
+                span_index = len(spans)
+                spans.append((start_token, end_token))
+            aannos.append(span_index)
+
+        token_mapping = torch.zeros(len(spans), num_token)
+        for j, (start, end) in enumerate(spans):
+            token_mapping[j, start:end] = 1
+        sanno_mapping = torch.zeros(len(spans), len(sanno_start))
+        for i, index in enumerate(sannos):
+            sanno_mapping[index, i] = 1
+        aanno_mapping = torch.zeros(len(spans), len(aanno_start))
+        for i, index in enumerate(aannos):
+            aanno_mapping[index, i] = 1
+        return token_mapping, sanno_mapping, aanno_mapping
+    span[span_contains_token.backward, span_equal_annotation.backward, anchor_equal_annotation.backward] = JointSensor(token['offset'], span_annotation['start'], span_annotation['end'], anchor_annotation['start'], anchor_annotation['end'], forward=token_to_span_fn)
+    span['emb'] = FunctionalSensor(span_contains_token.backward('emb'), forward=lambda x: x)
+
+    document_contains_span = document.relate_to(span)[0]
+    span[document_contains_span.forward] = EdgeSensor(span_contains_token.backward(document_contains_token.forward, fn=lambda x: x.max(dim=1)[0]), relation=document_contains_span.forward, mode='forward', forward=lambda x: x)
+
+    def token_label(span_token):
+        num_token = span_token.shape[1]
+
+        token_b = torch.zeros(num_token)
+        idx_b = (span_token - torch.nn.functional.pad(span_token, pad=[1,0])[:, :-1] > 0).nonzero()[:, 1]
+        token_b[idx_b] = 1
+        token_i = torch.zeros(num_token)
+        idx_i = span_token.nonzero()[:, 1]
+        token_i[idx_i] = 1
+        token_i -= token_b
+        token_o = torch.ones(num_token)
+        token_o -= token_i +token_b
+
+        return token_b, token_i, token_o
+
+    token[token_b, token_i, token_o] = FunctionalSensor(span[span_contains_token.backward], forward=token_label, label=True)
+    token[token_b] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
+    token[token_i] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
+    token[token_o] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
 
     # span -> base types
     for concept in find_is_a(entities_graph, span):
         print(f'Creating learner/reader for span -> {concept}')
-        span[concept] = ModuleLearner('emb', module=torch.nn.Linear(768*2, 2))
+        span[concept] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
         # span_annotation[concept] = LabelConstantSensor(concept=concept.name)
         # span[concept] = ConstantSensor(data=, label=True)
 
@@ -72,7 +121,7 @@ def token_to_span_fn(token_index):
         if '.' in concept.name:  # skip 'Class.', 'Role.', etc.
             continue
         print(f'Creating learner/reader for entity -> {concept}')
-        span[concept] = ModuleLearner('emb', module=torch.nn.Linear(768*2, 2))
+        span[concept] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
         span_annotation[concept] = LabelConstantSensor('type', concept=concept.name)
         # span[concept] = ConstantSensor(data=, label=True)
 
@@ -81,7 +130,7 @@ def token_to_span_fn(token_index):
             if '.' in sub_concept.name:  # skip 'Class.', 'Role.', etc.
                 continue
             print(f'Creating learner/reader for {concept} -> {sub_concept}')
-            span[sub_concept] = ModuleLearner('emb', module=torch.nn.Linear(768*2, 2))
+            span[sub_concept] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
             span_annotation[sub_concept] = LabelConstantSensor('subtype', concept=sub_concept.name)
             # span[sub_concept] = ConstantSensor(data=, label=True)
 
@@ -89,32 +138,37 @@ def token_to_span_fn(token_index):
     value = entities_graph['value']
     for concept in find_is_a(entities_graph, value):
         print(f'Creating learner/reader for value -> {concept}')
-        span[concept] = ModuleLearner('emb', module=torch.nn.Linear(768*2, 2))
+        span[concept] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
         span_annotation[concept] = LabelConstantSensor('type', concept=concept.name)
         # span[concept] = ConstantSensor(data=, label=True)
 
         # value -> sub classes
         for sub_concept in find_is_a(entities_graph, concept):
             print(f'Creating learner/reader for {concept} -> {sub_concept}')
-            span[sub_concept] = ModuleLearner('emb', module=torch.nn.Linear(768*2, 2))
+            span[sub_concept] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
             span_annotation[sub_concept] = LabelConstantSensor('subtype', concept=sub_concept.name)
             # span[sub_concept] = ConstantSensor(data=, label=True)
 
     # span -> pair
-    pair['index'] = CandidateSensor(span['index'], forward=lambda *_: True)
+    arg1, arg2 = pair.relate_to(span)
+    pair[arg1.backward, arg2.backward] = CompositionCandidateSensor(
+        span['emb'],
+        relations=(arg1.backward, arg2.backward),
+        forward=lambda *_: True)
+    # pair['index'] = CandidateSensor(span['emb'], forward=lambda *_: True)
     pair['emb'] = FunctionalSensor(span['emb'], forward=span_to_pair_emb)
 
     # event
     for concept in find_is_a(events_graph, event):
         print(f'Creating learner/reader for event -> {concept}')
-        span[concept] = ModuleLearner('emb', module=torch.nn.Linear(768*2, 2))
+        span[concept] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
         # span_annotation[concept] = LabelConstantSensor('type', concept=concept.name)
         # span[concept] = ConstantSensor(data=, label=True)
 
         # event sub classes
         for sub_concept in find_is_a(events_graph, concept):
             print(f'Creating learner/reader for {concept} -> {sub_concept}')
-            span[sub_concept] = ModuleLearner('emb', module=torch.nn.Linear(768*2, 2))
+            span[sub_concept] = ModuleLearner('emb', module=torch.nn.Linear(768, 2))
             # span_annotation[sub_concept] = LabelConstantSensor('subtype', concept=sub_concept.name)
             # span[sub_concept] = ConstantSensor(data=, label=True)
 
@@ -122,10 +176,10 @@ def token_to_span_fn(token_index):
             # pair -> event argument
             for event_arg in find_event_arg(events_graph, sub_concept):
                 print(f'Creating learner/reader for pair -> {sub_concept.name}\'s {event_arg.name}')
-                pair[event_arg] = ModuleLearner('emb', module=torch.nn.Linear(768*4, 2))
+                pair[event_arg] = ModuleLearner('emb', module=torch.nn.Linear(768*2, 2))
                 # pair[event_arg] = ?
 
     # program = POIProgram(graph, poi=(span, pair))
-    program = PrimalDualProgram(graph, PoiModel, poi=(span, pair))
+    program = PrimalDualProgram(graph, PoiModel, poi=(document, token, span, pair))
 
     return program
diff --git a/examples/ACE05/models.py b/examples/ACE05/models.py
index 117258d11..383d8e6bd 100644
--- a/examples/ACE05/models.py
+++ b/examples/ACE05/models.py
@@ -22,8 +22,28 @@ def cartesian_concat(*inputs):
 TRANSFORMER_MODEL = 'bert-base-uncased'
 
 
-def Tokenizer():
-    return BertTokenizerFast.from_pretrained(TRANSFORMER_MODEL)
+class Tokenizer():
+    def __init__(self) -> None:
+        self.tokenizer = BertTokenizerFast.from_pretrained(TRANSFORMER_MODEL)
+
+    def __call__(self, text):
+        if isinstance(text, str):
+            text = [text]
+        tokens = self.tokenizer(text, padding=True, return_tensors='pt', return_offsets_mapping=True)
+
+        ids = tokens['input_ids']
+        mask = tokens['attention_mask']
+        offset = tokens['offset_mapping']
+
+        idx = mask.nonzero()[:, 0].unsqueeze(-1)
+        mapping = torch.zeros(idx.shape[0], idx.max()+1)
+        mapping.scatter_(1, idx, 1)
+
+        mask = mask.bool()
+        ids = ids.masked_select(mask)
+        offset = torch.stack((offset[:,:,0].masked_select(mask), offset[:,:,1].masked_select(mask)), dim=-1)
+
+        return mapping, ids, offset 
 
 
 # emb_model = BertModel.from_pretrained(TRANSFORMER_MODEL)
diff --git a/examples/ACE05/requirements.txt b/examples/ACE05/requirements.txt
index 6e4c5b799..1a7b4451f 100644
--- a/examples/ACE05/requirements.txt
+++ b/examples/ACE05/requirements.txt
@@ -1,1 +1,1 @@
-transformers==3.0.2
+transformers==3.1
diff --git a/examples/ACE05/sensors/readerSensor.py b/examples/ACE05/sensors/readerSensor.py
index 61973e9e6..500b7de65 100644
--- a/examples/ACE05/sensors/readerSensor.py
+++ b/examples/ACE05/sensors/readerSensor.py
@@ -52,7 +52,7 @@ def __init__(self, *pres, edges=None, forward=None, label=False, device='auto',
         self.forward_ = forward
         self.concept_name = concept
 
-    def forward(self, datanode, match):
+    def forward(self, match, datanode):
         if len(datanode.getEqualTo(conceptName=self.concept_name)):
             return True
         else:
diff --git a/examples/CIFAR10/Sensors/sensors.py b/examples/CIFAR10/Sensors/sensors.py
new file mode 100644
index 000000000..5353b61f9
--- /dev/null
+++ b/examples/CIFAR10/Sensors/sensors.py
@@ -0,0 +1,22 @@
+from regr.sensor.pytorch.sensors import TorchSensor
+import spacy
+from typing import Any
+import torch
+
+
+class SentenceRepSensor(TorchSensor):
+    def __init__(self, *pres, edges=None, label=False):
+        super().__init__(*pres, edges=None, label=False)
+        self.nlp = spacy.load('en_core_web_sm')
+
+    def forward(self,) -> Any:
+        email = self.nlp(self.inputs[0])
+        return torch.from_numpy(email.vector).to(device=self.device)
+
+
+class ForwardPresenceSensor(TorchSensor):
+    def forward(self,) -> Any:
+        if self.inputs[0]:
+            return torch.ones(1).to(self.device)
+        else:
+            return torch.zeros(1).to(self.device)
diff --git a/examples/CIFAR10/graph.py b/examples/CIFAR10/graph.py
new file mode 100644
index 000000000..469c0154c
--- /dev/null
+++ b/examples/CIFAR10/graph.py
@@ -0,0 +1,23 @@
+from regr.graph import Graph, Concept, Relation
+from regr.graph.logicalConstrain import nandL, orL, andL, existsL, notL, atLeastL, atMostL, ifL
+from regr.graph.relation import disjoint
+
+Graph.clear()
+Concept.clear()
+Relation.clear()
+
+with Graph('CIFAR10') as graph:
+    image = Concept(name='image')
+
+    airplane = image(name='airplane')
+    dog = image(name='dog')
+    truck = image(name='truck')
+    automobile = image(name='automobile')
+    bird = image(name='bird')
+    cat = image(name='cat')
+    deer = image(name='deer')
+    frog = image(name='frog')
+    horse = image(name='horse')
+    ship = image(name='ship')
+
+    nandL(truck, dog, airplane, automobile, bird, cat, deer, frog, horse, ship)
diff --git a/examples/CIFAR10/main.py b/examples/CIFAR10/main.py
new file mode 100644
index 000000000..c1a29efce
--- /dev/null
+++ b/examples/CIFAR10/main.py
@@ -0,0 +1,234 @@
+import sys
+import torch
+from regr.program.model.pytorch import PoiModel, IMLModel
+from regr.program.model.primaldual import PrimalDualModel
+from regr.program.metric import MacroAverageTracker, PRF1Tracker
+import matplotlib.pyplot as plt
+from torchvision import datasets, transforms
+from PIL import Image
+import torch.nn.functional as F
+import torch.nn as nn
+import os,pickle
+import numpy as np
+from regr.program.loss import NBCrossEntropyLoss
+
+sys.path.append('.')
+sys.path.append('../..')
+
+def prediction_softmax(pr, gt):
+    return torch.softmax(pr.data, dim=-1)
+
+
+class ImageNetwork(torch.nn.Module):
+    def __init__(self, n_outputs=2):
+        super(ImageNetwork, self).__init__()
+        self.num_outputs = n_outputs
+        self.conv1 = nn.Conv2d(3, 6, 5)
+        self.pool = nn.MaxPool2d(2, 2)
+        self.conv2 = nn.Conv2d(6, 16, 5)
+
+
+        # self.fc = nn.Linear(16 * 5 * 5, n_outputs)
+
+        # self.fc1 = nn.Linear(16 * 5 * 5, 120)
+        # self.fc2 = nn.Linear(120, 84)
+        # self.fc = nn.Linear(84, n_outputs)
+
+    def forward(self, x):
+        x = F.relu(self.conv1(x))
+        x = self.pool(x)
+        x = F.relu(self.conv2(x))
+        x = self.pool(x)
+        x = x.view(-1, 16 * 5 * 5)
+        # x = F.relu(self.fc1(x))
+        # x = F.relu(self.fc2(x))
+        # x = self.fc(x)
+        return x
+
+class ImageModel(PrimalDualModel):
+    def __init__(self, graph):
+        super().__init__(
+            graph,
+            loss=MacroAverageTracker(NBCrossEntropyLoss()),
+            metric=PRF1Tracker())
+
+from graph import graph, image, truck, dog, airplane, automobile, bird, cat, deer, frog, horse, ship
+
+def model_declaration():
+    from regr.sensor.pytorch.sensors import ReaderSensor
+    from regr.sensor.pytorch.learners import ModuleLearner
+    from regr.program import LearningBasedProgram
+    from torch import nn
+    graph.detach()
+    image = graph['image']
+    airplane = graph['airplane']
+    dog = graph['dog']
+    truck = graph['truck']
+    automobile = graph['automobile']
+    bird = graph['bird']
+    cat = graph['cat']
+    deer = graph['deer']
+    frog = graph['frog']
+    horse = graph['horse']
+    ship = graph['ship']
+
+    image['pixels'] = ReaderSensor(keyword='pixels')
+    image[airplane] = ReaderSensor(keyword='airplane',label=True)
+    image[dog] = ReaderSensor(keyword='dog',label=True)
+    image[truck] = ReaderSensor(keyword='truck',label=True)
+    image[automobile] = ReaderSensor(keyword='automobile',label=True)
+    image[bird] = ReaderSensor(keyword='bird',label=True)
+    image[cat] = ReaderSensor(keyword='cat',label=True)
+    image[deer] = ReaderSensor(keyword='deer',label=True)
+    image[frog] = ReaderSensor(keyword='frog',label=True)
+    image[horse] = ReaderSensor(keyword='horse',label=True)
+    image[ship] = ReaderSensor(keyword='ship',label=True)
+
+    image['emb'] = ModuleLearner('pixels', module=ImageNetwork())
+    image[airplane] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[dog] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[truck] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[automobile] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[bird] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[cat] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[deer] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[frog] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[horse] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[ship] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+
+    program = LearningBasedProgram(graph, ImageModel)
+
+    return program
+
+class CIFAR10_1(datasets.CIFAR10):
+
+    def __init__(self, root, train=True, transform=None, target_transform=None,
+                 download=False):
+
+        super(CIFAR10_1, self).__init__(root, transform=transform,
+                                      target_transform=target_transform, download=download)
+
+        self.train = train  # training set or test set
+
+        if not self._check_integrity():
+            raise RuntimeError('Dataset not found or corrupted.' +
+                               ' You can use download=True to download it')
+
+        if self.train:
+            downloaded_list = self.train_list
+        else:
+            downloaded_list = self.test_list
+
+        self.data = []
+        self.targets = []
+
+        # now load the picked numpy arrays
+        for file_name, checksum in downloaded_list:
+            file_path = os.path.join(self.root, self.base_folder, file_name)
+            with open(file_path, 'rb') as f:
+                if sys.version_info[0] == 2:
+                    entry = pickle.load(f)
+                else:
+                    entry = pickle.load(f, encoding='latin1')
+
+                self.data.append(entry['data'])
+                if 'labels' in entry:
+                    self.targets.extend(entry['labels'])
+                else:
+                    self.targets.extend(entry['fine_labels'])
+
+        self.data = np.vstack(self.data).reshape(-1, 3, 32, 32)
+        self.data = self.data.transpose((0, 2, 3, 1))  # convert to HWC
+
+        self._load_meta()
+
+    def __getitem__(self, index):
+        """
+        Args:
+            index (int): Index
+
+        Returns:
+            tuple: (image, target) where target is index of the target class.
+        """
+        img, target = self.data[index], self.targets[index]
+
+        # doing this so that it is consistent with all other datasets
+        # to return a PIL Image
+        img = Image.fromarray(img)
+
+        if self.transform is not None:
+            img = self.transform(img)
+        if self.target_transform is not None:
+            target = self.target_transform(target)
+
+        img = img.unsqueeze(0)
+        target_dict = {0:'airplane',1: 'automobile', 2: 'bird', 3: 'cat', 4: 'deer', 5: 'dog', 6: 'frog', 7:'horse',8: 'ship', 9: 'truck'}
+        dict = {}
+        dict['pixels'] = img
+        for i in range(10):
+            dict[target_dict[i]] = [0]
+        dict[target_dict[target]] = [1]
+        return dict
+
+def load_cifar10(train=True, root='./data/', size=32):
+    CIFAR100_TRAIN_MEAN = (0.5071, 0.4867, 0.4408)
+    CIFAR100_TRAIN_STD = (0.2675, 0.2565, 0.2761)
+
+    if train:
+        transform = transforms.Compose(
+            [transforms.Resize(size),
+             transforms.RandomCrop(size, padding=round(size/8)),
+             transforms.RandomHorizontalFlip(),
+             transforms.ToTensor(),
+             transforms.Normalize(CIFAR100_TRAIN_MEAN, CIFAR100_TRAIN_STD)])
+    else:
+        transform = transforms.Compose(
+            [transforms.Resize(size),
+             transforms.ToTensor(),
+             transforms.Normalize(CIFAR100_TRAIN_MEAN, CIFAR100_TRAIN_STD)])
+
+    return CIFAR10_1(root=root, train=train, transform=transform,download=True)
+
+def main():
+    program = model_declaration()
+
+    ### load data
+    trainset = load_cifar10(train=True)
+    testset = load_cifar10(train=False)
+
+    program.train(trainset, train_epoch_num=10, Optim=lambda param: torch.optim.SGD(param, lr=.001))
+
+    label_list = ['airplane', 'automobile','bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
+    counter = 0
+    for datanode in program.populate(dataset=testset):
+        print('>>>>>**********************************')
+        print('----------before ILP---------')
+        for label in label_list:
+            print(label, datanode.getAttribute(eval(label)).softmax(-1))
+
+        datanode.inferILPConstrains('dog', 'truck', 'airplane',
+                                    'automobile', 'bird', 'cat',
+                                    'deer', 'frog', 'horse', 'ship',fun=None)
+        print('----------after ILP---------')
+        prediction = ' '
+        for label in label_list:
+            predt_label = datanode.getAttribute(eval(label), 'ILP').item()
+            if predt_label == 1.0:
+                prediction = label
+            print('inference ',label, predt_label )
+        d = datanode.getAttributes()['pixels'].numpy()
+        plt.figure()
+        plt.imshow((d[0,:,:]),interpolation='nearest', aspect='auto')
+        plt.text(5, 5, 'prediction: '+str(prediction), color='white',fontsize=15 )
+        plt.savefig(str(counter)+'.png')
+        # plt.show()
+        counter += 1
+        if counter == 20:
+            break
+
+
+
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/CIFAR10/main_iml.py b/examples/CIFAR10/main_iml.py
new file mode 100644
index 000000000..698199637
--- /dev/null
+++ b/examples/CIFAR10/main_iml.py
@@ -0,0 +1,234 @@
+import sys
+import torch
+from regr.program.model.pytorch import PoiModel, IMLModel
+from regr.program.model.primaldual import PrimalDualModel
+from regr.program.metric import MacroAverageTracker, PRF1Tracker
+import matplotlib.pyplot as plt
+from torchvision import datasets, transforms
+from PIL import Image
+import torch.nn.functional as F
+import torch.nn as nn
+import os,pickle
+import numpy as np
+from regr.program.loss import NBCrossEntropyLoss, BCEWithLogitsIMLoss
+
+sys.path.append('.')
+sys.path.append('../..')
+
+def prediction_softmax(pr, gt):
+    return torch.softmax(pr.data, dim=-1)
+
+
+class ImageNetwork(torch.nn.Module):
+    def __init__(self, n_outputs=2):
+        super(ImageNetwork, self).__init__()
+        self.num_outputs = n_outputs
+        self.conv1 = nn.Conv2d(3, 6, 5)
+        self.pool = nn.MaxPool2d(2, 2)
+        self.conv2 = nn.Conv2d(6, 16, 5)
+
+
+        # self.fc = nn.Linear(16 * 5 * 5, n_outputs)
+
+        # self.fc1 = nn.Linear(16 * 5 * 5, 120)
+        # self.fc2 = nn.Linear(120, 84)
+        # self.fc = nn.Linear(84, n_outputs)
+
+    def forward(self, x):
+        x = F.relu(self.conv1(x))
+        x = self.pool(x)
+        x = F.relu(self.conv2(x))
+        x = self.pool(x)
+        x = x.view(-1, 16 * 5 * 5)
+        # x = F.relu(self.fc1(x))
+        # x = F.relu(self.fc2(x))
+        # x = self.fc(x)
+        return x
+
+class ImageModel(IMLModel):
+    def __init__(self, graph):
+        super().__init__(
+            graph,
+            loss=MacroAverageTracker(BCEWithLogitsIMLoss(lmbd=0.5)),
+            metric=PRF1Tracker())
+
+from graph import graph, image, truck, dog, airplane, automobile, bird, cat, deer, frog, horse, ship
+
+def model_declaration():
+    from regr.sensor.pytorch.sensors import ReaderSensor
+    from regr.sensor.pytorch.learners import ModuleLearner
+    from regr.program import LearningBasedProgram
+    from torch import nn
+    graph.detach()
+    image = graph['image']
+    airplane = graph['airplane']
+    dog = graph['dog']
+    truck = graph['truck']
+    automobile = graph['automobile']
+    bird = graph['bird']
+    cat = graph['cat']
+    deer = graph['deer']
+    frog = graph['frog']
+    horse = graph['horse']
+    ship = graph['ship']
+
+    image['pixels'] = ReaderSensor(keyword='pixels')
+    image[airplane] = ReaderSensor(keyword='airplane',label=True)
+    image[dog] = ReaderSensor(keyword='dog',label=True)
+    image[truck] = ReaderSensor(keyword='truck',label=True)
+    image[automobile] = ReaderSensor(keyword='automobile',label=True)
+    image[bird] = ReaderSensor(keyword='bird',label=True)
+    image[cat] = ReaderSensor(keyword='cat',label=True)
+    image[deer] = ReaderSensor(keyword='deer',label=True)
+    image[frog] = ReaderSensor(keyword='frog',label=True)
+    image[horse] = ReaderSensor(keyword='horse',label=True)
+    image[ship] = ReaderSensor(keyword='ship',label=True)
+
+    image['emb'] = ModuleLearner('pixels', module=ImageNetwork())
+    image[airplane] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[dog] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[truck] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[automobile] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[bird] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[cat] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[deer] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[frog] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[horse] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+    image[ship] = ModuleLearner('emb', module=nn.Linear(16 * 5 * 5, 2))
+
+    program = LearningBasedProgram(graph, ImageModel)
+
+    return program
+
+class CIFAR10_1(datasets.CIFAR10):
+
+    def __init__(self, root, train=True, transform=None, target_transform=None,
+                 download=False):
+
+        super(CIFAR10_1, self).__init__(root, transform=transform,
+                                      target_transform=target_transform, download=download)
+
+        self.train = train  # training set or test set
+
+        if not self._check_integrity():
+            raise RuntimeError('Dataset not found or corrupted.' +
+                               ' You can use download=True to download it')
+
+        if self.train:
+            downloaded_list = self.train_list
+        else:
+            downloaded_list = self.test_list
+
+        self.data = []
+        self.targets = []
+
+        # now load the picked numpy arrays
+        for file_name, checksum in downloaded_list:
+            file_path = os.path.join(self.root, self.base_folder, file_name)
+            with open(file_path, 'rb') as f:
+                if sys.version_info[0] == 2:
+                    entry = pickle.load(f)
+                else:
+                    entry = pickle.load(f, encoding='latin1')
+
+                self.data.append(entry['data'])
+                if 'labels' in entry:
+                    self.targets.extend(entry['labels'])
+                else:
+                    self.targets.extend(entry['fine_labels'])
+
+        self.data = np.vstack(self.data).reshape(-1, 3, 32, 32)
+        self.data = self.data.transpose((0, 2, 3, 1))  # convert to HWC
+
+        self._load_meta()
+
+    def __getitem__(self, index):
+        """
+        Args:
+            index (int): Index
+
+        Returns:
+            tuple: (image, target) where target is index of the target class.
+        """
+        img, target = self.data[index], self.targets[index]
+
+        # doing this so that it is consistent with all other datasets
+        # to return a PIL Image
+        img = Image.fromarray(img)
+
+        if self.transform is not None:
+            img = self.transform(img)
+        if self.target_transform is not None:
+            target = self.target_transform(target)
+
+        img = img.unsqueeze(0)
+        target_dict = {0:'airplane',1: 'automobile', 2: 'bird', 3: 'cat', 4: 'deer', 5: 'dog', 6: 'frog', 7:'horse',8: 'ship', 9: 'truck'}
+        dict = {}
+        dict['pixels'] = img
+        for i in range(10):
+            dict[target_dict[i]] = [0]
+        dict[target_dict[target]] = [1]
+        return dict
+
+def load_cifar10(train=True, root='./data/', size=32):
+    CIFAR100_TRAIN_MEAN = (0.5071, 0.4867, 0.4408)
+    CIFAR100_TRAIN_STD = (0.2675, 0.2565, 0.2761)
+
+    if train:
+        transform = transforms.Compose(
+            [transforms.Resize(size),
+             transforms.RandomCrop(size, padding=round(size/8)),
+             transforms.RandomHorizontalFlip(),
+             transforms.ToTensor(),
+             transforms.Normalize(CIFAR100_TRAIN_MEAN, CIFAR100_TRAIN_STD)])
+    else:
+        transform = transforms.Compose(
+            [transforms.Resize(size),
+             transforms.ToTensor(),
+             transforms.Normalize(CIFAR100_TRAIN_MEAN, CIFAR100_TRAIN_STD)])
+
+    return CIFAR10_1(root=root, train=train, transform=transform,download=True)
+
+def main():
+    program = model_declaration()
+
+    ### load data
+    trainset = load_cifar10(train=True)
+    testset = load_cifar10(train=False)
+
+    program.train(trainset, train_epoch_num=10, Optim=lambda param: torch.optim.SGD(param, lr=.001))
+
+    label_list = ['airplane', 'automobile','bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
+    counter = 0
+    for datanode in program.populate(dataset=testset):
+        print('>>>>>**********************************')
+        print('----------before ILP---------')
+        for label in label_list:
+            print(label, datanode.getAttribute(eval(label)).softmax(-1))
+
+        datanode.inferILPConstrains('dog', 'truck', 'airplane',
+                                    'automobile', 'bird', 'cat',
+                                    'deer', 'frog', 'horse', 'ship',fun=None)
+        print('----------after ILP---------')
+        prediction = ' '
+        for label in label_list:
+            predt_label = datanode.getAttribute(eval(label), 'ILP').item()
+            if predt_label == 1.0:
+                prediction = label
+            print('inference ',label, predt_label )
+        d = datanode.getAttributes()['pixels'].numpy()
+        plt.figure()
+        plt.imshow((d[0,:,:]),interpolation='nearest', aspect='auto')
+        plt.text(5, 5, 'prediction: '+str(prediction), color='white',fontsize=15 )
+        plt.savefig(str(counter)+'.png')
+        # plt.show()
+        counter += 1
+        if counter == 20:
+            break
+
+
+
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/Email_Spam/main.py b/examples/Email_Spam/main.py
index 30e7b72c0..09c88a296 100644
--- a/examples/Email_Spam/main.py
+++ b/examples/Email_Spam/main.py
@@ -7,7 +7,7 @@
 
 
 def model_declaration():
-    from regr.sensor.pytorch.sensors import ReaderSensor, TorchEdgeReaderSensor, ForwardEdgeSensor, ConstantSensor, ConcatSensor
+    from regr.sensor.pytorch.sensors import ReaderSensor, ForwardEdgeSensor, ConstantSensor, ConcatSensor
     from regr.sensor.pytorch.learners import ModuleLearner
     from regr.sensor.pytorch.relation_sensors import CandidateReaderSensor
     from regr.program import LearningBasedProgram
diff --git a/examples/Propara/Propara.ipynb b/examples/Propara/Propara.ipynb
index 0a9d30311..dbc19fe50 100644
--- a/examples/Propara/Propara.ipynb
+++ b/examples/Propara/Propara.ipynb
@@ -43,22 +43,22 @@
      "data": {
       "text/plain": [
        "{'id': '37',\n",
-       " 'entities': ['plant; animal'],\n",
+       " 'entities': ['bones'],\n",
        " 'steps': ['A plant of animal dies in a watery environment.',\n",
        "  'Is buried in mud and silt.',\n",
        "  'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
        "  'Over time sediment builds over the top and hardens into rock.',\n",
        "  'As the bone decays mineral seeps in replacing it.',\n",
        "  'Fossils are formed.'],\n",
-       " 'entity_step': [[0.9683084487915039,\n",
-       "   0.029291482642292976,\n",
-       "   0.0024000774137675762],\n",
-       "  [0.012266993522644043, 0.002779645612463355, 0.9849532842636108],\n",
-       "  [0.006055360194295645, 0.0023835168685764074, 0.9915611147880554],\n",
-       "  [0.0024350169114768505, 0.0022026486694812775, 0.9953623414039612],\n",
-       "  [0.0015909943031147122, 0.0026168148033320904, 0.9957921504974365],\n",
-       "  [0.0015094410628080368, 0.002839647000655532, 0.9956509470939636],\n",
-       "  [0.0016313738888129592, 0.0032306257635354996, 0.9951379895210266]]}"
+       " 'entity_step': [[0.005079575348645449,\n",
+       "   0.003536409232765436,\n",
+       "   0.9913840293884277],\n",
+       "  [0.0023296682629734278, 0.002440220210701227, 0.9952301979064941],\n",
+       "  [0.004893037490546703, 0.004029534757137299, 0.9910774230957031],\n",
+       "  [0.9432244300842285, 0.05380963161587715, 0.002965932944789529],\n",
+       "  [0.9029137492179871, 0.09116195142269135, 0.005924322176724672],\n",
+       "  [0.005038038361817598, 0.008660664781928062, 0.9863013029098511],\n",
+       "  [0.006529272999614477, 0.009216826409101486, 0.9842538833618164]]}"
       ]
      },
      "execution_count": 3,
@@ -69,7 +69,7 @@
    "source": [
     "with open(\"updated_test_data.json\", \"r\") as file:\n",
     "    data = json.load(file)\n",
-    "data[0]"
+    "data[2]"
    ]
   },
   {
@@ -111,7 +111,7 @@
     "            values.append([1 - item['entity_step'][step][0], item['entity_step'][step][0]])\n",
     "        return torch.tensor(values)\n",
     "    \n",
-    "    def getunkownval(self, item):\n",
+    "    def getunknownval(self, item):\n",
     "        values = []\n",
     "        for step in range(len(item['steps']) + 1):\n",
     "            values.append([1 - item['entity_step'][step][1], item['entity_step'][step][1]])\n",
@@ -328,39 +328,39 @@
        "          [0., 0., 0., 0., 1., 0., 0.],\n",
        "          [0., 0., 0., 0., 0., 1., 0.],\n",
        "          [0., 0., 0., 0., 0., 0., 1.]])),\n",
-       " 'create': tensor([[9.9996e-01, 3.6113e-05],\n",
-       "         [9.9169e-01, 8.3119e-03],\n",
-       "         [9.9540e-01, 4.5985e-03],\n",
-       "         [9.9581e-01, 4.1883e-03],\n",
-       "         [9.9567e-01, 4.3308e-03],\n",
-       "         [9.9516e-01, 4.8409e-03]]),\n",
-       " 'destroy': tensor([[0.0174, 0.9826],\n",
-       "         [0.9851, 0.0149],\n",
-       "         [0.9916, 0.0084],\n",
-       "         [0.9954, 0.0046],\n",
-       "         [0.9958, 0.0042],\n",
-       "         [0.9957, 0.0043]]),\n",
-       " 'entities': ['plant; animal'],\n",
-       " 'known': tensor([[0.0317, 0.9683],\n",
-       "         [0.9877, 0.0123],\n",
-       "         [0.9939, 0.0061],\n",
-       "         [0.9976, 0.0024],\n",
-       "         [0.9984, 0.0016],\n",
-       "         [0.9985, 0.0015],\n",
-       "         [0.9984, 0.0016]]),\n",
-       " 'non_existence': tensor([[0.9976, 0.0024],\n",
-       "         [0.0150, 0.9850],\n",
-       "         [0.0084, 0.9916],\n",
-       "         [0.0046, 0.9954],\n",
-       "         [0.0042, 0.9958],\n",
-       "         [0.0043, 0.9957],\n",
-       "         [0.0049, 0.9951]]),\n",
-       " 'other': tensor([[0.9826, 0.0174],\n",
-       "         [0.0232, 0.9768],\n",
-       "         [0.0130, 0.9870],\n",
-       "         [0.0088, 0.9912],\n",
-       "         [0.0085, 0.9915],\n",
-       "         [0.0092, 0.9908]]),\n",
+       " 'create': tensor([[9.9527e-01, 4.7288e-03],\n",
+       "         [9.9112e-01, 8.8800e-03],\n",
+       "         [1.1862e-02, 9.8814e-01],\n",
+       "         [9.9705e-01, 2.9484e-03],\n",
+       "         [9.9992e-01, 8.1156e-05],\n",
+       "         [9.8447e-01, 1.5530e-02]]),\n",
+       " 'destroy': tensor([[9.9143e-01, 8.5749e-03],\n",
+       "         [9.9527e-01, 4.7273e-03],\n",
+       "         [9.9997e-01, 2.6464e-05],\n",
+       "         [9.9409e-01, 5.9068e-03],\n",
+       "         [1.9542e-02, 9.8046e-01],\n",
+       "         [9.8652e-01, 1.3483e-02]]),\n",
+       " 'entities': ['bones'],\n",
+       " 'known': tensor([[0.9949, 0.0051],\n",
+       "         [0.9977, 0.0023],\n",
+       "         [0.9951, 0.0049],\n",
+       "         [0.0568, 0.9432],\n",
+       "         [0.0971, 0.9029],\n",
+       "         [0.9950, 0.0050],\n",
+       "         [0.9935, 0.0065]]),\n",
+       " 'non_existence': tensor([[0.0086, 0.9914],\n",
+       "         [0.0048, 0.9952],\n",
+       "         [0.0089, 0.9911],\n",
+       "         [0.9970, 0.0030],\n",
+       "         [0.9941, 0.0059],\n",
+       "         [0.0137, 0.9863],\n",
+       "         [0.0157, 0.9843]]),\n",
+       " 'other': tensor([[0.0133, 0.9867],\n",
+       "         [0.0136, 0.9864],\n",
+       "         [0.9882, 0.0118],\n",
+       "         [0.0089, 0.9911],\n",
+       "         [0.9805, 0.0195],\n",
+       "         [0.0290, 0.9710]]),\n",
        " 'procedureID': ['37'],\n",
        " 'steps': (tensor([[1.],\n",
        "          [1.],\n",
@@ -376,13 +376,13 @@
        "   'Over time sediment builds over the top and hardens into rock.',\n",
        "   'As the bone decays mineral seeps in replacing it.',\n",
        "   'Fossils are formed.']),\n",
-       " 'unkown': tensor([[0.9707, 0.0293],\n",
-       "         [0.9972, 0.0028],\n",
+       " 'unknown': tensor([[0.9965, 0.0035],\n",
        "         [0.9976, 0.0024],\n",
-       "         [0.9978, 0.0022],\n",
-       "         [0.9974, 0.0026],\n",
-       "         [0.9972, 0.0028],\n",
-       "         [0.9968, 0.0032]])}"
+       "         [0.9960, 0.0040],\n",
+       "         [0.9462, 0.0538],\n",
+       "         [0.9088, 0.0912],\n",
+       "         [0.9913, 0.0087],\n",
+       "         [0.9908, 0.0092]])}"
       ]
      },
      "execution_count": 6,
@@ -392,21 +392,14 @@
    ],
    "source": [
     "ReaderObjectsIterator = ProparaReader(\"updated_test_data.json\")\n",
-    "next(iter(ReaderObjectsIterator))"
+    "list(iter(ReaderObjectsIterator))[2]"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 20,
    "metadata": {},
    "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Log file for dataNode is in: /home/hfaghihi/Framework/DomiKnowS/examples/Propara/datanode.log\n"
-     ]
-    },
     {
      "name": "stderr",
      "output_type": "stream",
@@ -439,18 +432,6 @@
     "    create = action(name=\"create\")\n",
     "    destroy = action(name=\"destroy\")\n",
     "    other = action(name=\"other\")\n",
-    "\n",
-    "    #LC1 : An action can not be create, destroy and other at the same time\n",
-    "    nandL(create, destroy, other)\n",
-    "    \n",
-    "    #LC2 : An action should at least be one of the create, destroy or other\n",
-    "    orL(create, destroy, other)\n",
-    "    \n",
-    "    #LC3 : A step can not be known_loc, unknown_loc and non_existence at the same time\n",
-    "    nandL(known_loc, unknown_loc, non_existence)\n",
-    "    \n",
-    "    #LC4 : A step should at least be one of known_loc, unknown_loc or non_existence\n",
-    "    orL(known_loc, unknown_loc, non_existence)\n",
     "    \n",
     "    #LC5 : If action is create then the first step should be non_existence and the second step can be either known_loc or unknown_loc\n",
     "    ifL(create, (\"x\", \"y\", ), andL(non_existence, (\"x\", ), orL(known_loc, (\"y\", ), unknown_loc, (\"y\", ))))\n",
@@ -464,17 +445,29 @@
     "    #LC8 : There should be at most one destroy\n",
     "    atMostL(1, (\"x\", ), destroy, (\"x\", ))\n",
     "    \n",
-    "    #LC9 : If (x1,x2) is create and (y1, y2) is destroy, then the pair(x2,y2) if before should have the property \"check\" equal to 1.\n",
+    "    #LC9 : If (x1,x2) is create and (y1, y2) is destroy, then the pair(x2,y2) in before should have the property \"check\" equal to 1.\n",
     "    # I will have to check if this eqL works if not will update it\n",
     "    ifL(andL(create, (\"x1\", \"x2\"), destroy, (\"y1\", \"y2\")), eqL(before, \"check\", 1), (\"x2\", \"y2\"))\n",
     "    \n",
+    "    #LC1 : An action can not be create, destroy and other at the same time\n",
+    "    nandL(create, destroy, other)\n",
+    "    \n",
+    "    #LC2 : An action should at least be one of the create, destroy or other\n",
+    "    orL(create, destroy, other)\n",
+    "    \n",
+    "    #LC3 : A step can not be known_loc, unknown_loc and non_existence at the same time\n",
+    "    nandL(known_loc, unknown_loc, non_existence)\n",
+    "    \n",
+    "    #LC4 : A step should at least be one of known_loc, unknown_loc or non_existence\n",
+    "    orL(known_loc, unknown_loc, non_existence)\n",
+    "    \n",
     "\n",
     "\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 21,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -502,7 +495,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 22,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -523,12 +516,12 @@
     "#     entity['raw'] = ReaderSensor(keyword='entities')\n",
     "\n",
     "    step[non_existence] = ReaderSensor(procedure_contain_step.forward, 'text', keyword='non_existence')\n",
-    "    step[unknown_loc] = ReaderSensor(procedure_contain_step.forward, 'text', keyword='known')\n",
-    "    step[known_loc] = ReaderSensor(procedure_contain_step.forward, 'text', keyword='unkown')\n",
+    "    step[unknown_loc] = ReaderSensor(procedure_contain_step.forward, 'text', keyword='unknown')\n",
+    "    step[known_loc] = ReaderSensor(procedure_contain_step.forward, 'text', keyword='known')\n",
     "    \n",
     "    step[non_existence] = ReaderSensor(keyword='non_existence', label=True)\n",
-    "    step[unknown_loc] = ReaderSensor(keyword='known', label=True)\n",
-    "    step[known_loc] = ReaderSensor(keyword='unkown', label=True)\n",
+    "    step[unknown_loc] = ReaderSensor(keyword='unknown', label=True)\n",
+    "    step[known_loc] = ReaderSensor(keyword='known', label=True)\n",
     "    \n",
     "    action[action_arg1.backward, action_arg2.backward] = JoinReaderSensor(step['text'], keyword='action')\n",
     "    \n",
@@ -556,7 +549,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 23,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -570,69 +563,157 @@
     "    dataset = ProparaReader(file='updated_test_data.json')  # Adding the info on the reader\n",
     "\n",
     "#     lbp.test(dataset, device='auto')\n",
-    "\n",
+    "    all_updates = []\n",
     "    for datanode in lbp.populate(dataset, device=\"cpu\"):\n",
-    "        print('datanode:', datanode)\n",
-    "        data1 = datanode.findDatanodes(select = step)[0].getAttribute(\"text\")\n",
-    "        print(data1)\n",
-    "        data1 = datanode.findDatanodes(select = step)[0].getAttribute(non_existence)\n",
-    "        print(data1)\n",
-    "        data1 = datanode.findDatanodes(select = before)[0].getAttribute(\"check\")\n",
-    "        print(data1)\n",
-    "        data1 = datanode.findDatanodes(select = action)[0].getAttribute(create)\n",
-    "        print(data1)\n",
-    "#         datanode.inferILPConstrains(create, destroy, other, non_existence, known_loc, unknown_loc, fun=None)\n",
+    "#         print('datanode:', datanode)\n",
+    "        datanode.inferILPConstrains(create, destroy, other, non_existence, known_loc, unknown_loc, fun=None)\n",
+    "        final_output = {\"id\": datanode.getAttribute('id'), \"steps\":[], \"actions\":[], \"steps_before\": [], \"actions_before\": []}\n",
+    "        for step_info in datanode.findDatanodes(select = step):\n",
+    "            k = step_info.getAttribute(known_loc, \"ILP\")\n",
+    "            u = step_info.getAttribute(unknown_loc, \"ILP\")\n",
+    "            n = step_info.getAttribute(non_existence, \"ILP\")\n",
+    "            final_output[\"steps\"].append((k,u,n))\n",
+    "            k = step_info.getAttribute(known_loc)\n",
+    "            u = step_info.getAttribute(unknown_loc)\n",
+    "            n = step_info.getAttribute(non_existence)\n",
+    "            final_output[\"steps_before\"].append((k,u,n))\n",
+    "        for action_info in datanode.findDatanodes(select = action):\n",
+    "            c = action_info.getAttribute(create, \"ILP\")\n",
+    "            d = action_info.getAttribute(destroy, \"ILP\")\n",
+    "            o = action_info.getAttribute(other, \"ILP\")\n",
+    "            final_output[\"actions\"].append((c,d,o))\n",
+    "            c = action_info.getAttribute(create)\n",
+    "            d = action_info.getAttribute(destroy)\n",
+    "            o = action_info.getAttribute(other)\n",
+    "            final_output[\"actions_before\"].append((c,d,o))\n",
+    "        all_updates.append(final_output)\n",
     "#         print('datanode:', datanode)\n",
     "#         print('inference spam:', datanode.getAttribute(Spam, 'ILP'))\n",
-    "#         print('inference regular:', datanode.getAttribute(Regular, 'ILP'))"
+    "#         print('inference regular:', datanode.getAttribute(Regular, 'ILP'))\n",
+    "    print(len(all_updates))\n",
+    "    return all_updates"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 24,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "datanode: procedure 0\n",
-      "step 0 information\n",
-      "tensor([0.9976, 0.0024])\n",
-      "tensor(0.)\n"
-     ]
-    },
-    {
-     "ename": "IndexError",
-     "evalue": "list index out of range",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[0;31mIndexError\u001b[0m                                Traceback (most recent call last)",
-      "\u001b[0;32m<ipython-input-15-263240bbee7e>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
-      "\u001b[0;32m<ipython-input-10-7874105c9dd9>\u001b[0m in \u001b[0;36mmain\u001b[0;34m()\u001b[0m\n\u001b[1;32m     18\u001b[0m         \u001b[0mdata1\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdatanode\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindDatanodes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mselect\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mbefore\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgetAttribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"check\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     19\u001b[0m         \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m         \u001b[0mdata1\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdatanode\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfindDatanodes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mselect\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0maction\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgetAttribute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcreate\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     21\u001b[0m         \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     22\u001b[0m \u001b[0;31m#         datanode.inferILPConstrains(create, destroy, other, non_existence, known_loc, unknown_loc, fun=None)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;31mIndexError\u001b[0m: list index out of range"
+      "236\n"
      ]
     }
    ],
    "source": [
-    "main()"
+    "fdata = main()"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 27,
    "metadata": {},
    "outputs": [
     {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "/home/hfaghihi/Framework/DomiKnowS/regr/sensor/pytorch/sensors.py:196: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n",
-      "  return torch.tensor(self.data, device=self.device)\n"
-     ]
+     "data": {
+      "text/plain": [
+       "{'id': '37',\n",
+       " 'steps': [(tensor([1.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([1.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([1.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64))],\n",
+       " 'actions': [(tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64)),\n",
+       "  (None, None, None),\n",
+       "  (tensor(1., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(1., device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(1., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(1., device='cuda:0', dtype=torch.float64))],\n",
+       " 'steps_before': [(tensor([0.9949, 0.0051]),\n",
+       "   tensor([0.9965, 0.0035]),\n",
+       "   tensor([0.0086, 0.9914])),\n",
+       "  (tensor([0.9977, 0.0023]),\n",
+       "   tensor([0.9976, 0.0024]),\n",
+       "   tensor([0.0048, 0.9952])),\n",
+       "  (tensor([0.9951, 0.0049]),\n",
+       "   tensor([0.9960, 0.0040]),\n",
+       "   tensor([0.0089, 0.9911])),\n",
+       "  (tensor([0.0568, 0.9432]),\n",
+       "   tensor([0.9462, 0.0538]),\n",
+       "   tensor([0.9970, 0.0030])),\n",
+       "  (tensor([0.0971, 0.9029]),\n",
+       "   tensor([0.9088, 0.0912]),\n",
+       "   tensor([0.9941, 0.0059])),\n",
+       "  (tensor([0.9950, 0.0050]),\n",
+       "   tensor([0.9913, 0.0087]),\n",
+       "   tensor([0.0137, 0.9863])),\n",
+       "  (tensor([0.9935, 0.0065]),\n",
+       "   tensor([0.9908, 0.0092]),\n",
+       "   tensor([0.0157, 0.9843]))],\n",
+       " 'actions_before': [(tensor([0.9953, 0.0047]),\n",
+       "   tensor([0.9914, 0.0086]),\n",
+       "   tensor([0.0133, 0.9867])),\n",
+       "  (tensor([0.9911, 0.0089]),\n",
+       "   tensor([0.9953, 0.0047]),\n",
+       "   tensor([0.0136, 0.9864])),\n",
+       "  (tensor([0.0119, 0.9881]),\n",
+       "   tensor([9.9997e-01, 2.6464e-05]),\n",
+       "   tensor([0.9882, 0.0118])),\n",
+       "  (tensor([0.9971, 0.0029]),\n",
+       "   tensor([0.9941, 0.0059]),\n",
+       "   tensor([0.0089, 0.9911])),\n",
+       "  (tensor([9.9992e-01, 8.1156e-05]),\n",
+       "   tensor([0.0195, 0.9805]),\n",
+       "   tensor([0.9805, 0.0195])),\n",
+       "  (tensor([0.9845, 0.0155]),\n",
+       "   tensor([0.9865, 0.0135]),\n",
+       "   tensor([0.0290, 0.9710]))]}"
+      ]
+     },
+     "execution_count": 27,
+     "metadata": {},
+     "output_type": "execute_result"
     }
    ],
+   "source": [
+    "fdata[2]\n",
+    "# dataset = ProparaReader(file='updated_test_data.json')  # Adding the info on the reader\n",
+    "# print(list(iter(dataset))[2]['non_existence'])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 46,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "from regr.graph import DataNodeBuilder\n",
     "from regr.sensor.sensor import Sensor\n",
@@ -644,8 +725,188 @@
     "for datanode in lbp.populate(list(dataset)[0:1], device=\"cpu\"):\n",
     "    pass\n",
     "dataset = ProparaReader(file='updated_test_data.json')\n",
+    "all_updates = []\n",
+    "for data in list(iter(dataset)):\n",
+    "    context = {\"graph\": graph}\n",
+    "    context.update(data)\n",
+    "    data_item = DataNodeBuilder(context)\n",
+    "    # data_item\n",
+    "    for sensor in procedure['id'].find(Sensor):\n",
+    "        sensor(data_item)\n",
+    "    for sensor in step['text'].find(Sensor):\n",
+    "        sensor(data_item)\n",
+    "    for sensor in step[procedure_contain_step.forward].find(Sensor):\n",
+    "        sensor(data_item)\n",
+    "    for sensor in step[known_loc].find(Sensor):\n",
+    "        sensor(data_item)\n",
+    "    for sensor in step[unknown_loc].find(Sensor):\n",
+    "        sensor(data_item)\n",
+    "    for sensor in step[non_existence].find(Sensor):\n",
+    "        sensor(data_item)\n",
+    "    for sensor in action[action_arg1.backward].find(Sensor):\n",
+    "        sensor(data_item)\n",
+    "    for sensor in action[action_arg2.backward].find(Sensor):\n",
+    "        sensor(data_item)\n",
+    "    for sensor in action[destroy].find(Sensor):\n",
+    "        sensor(data_item)\n",
+    "    for sensor in action[create].find(Sensor):\n",
+    "        sensor(data_item)\n",
+    "    for sensor in action[other].find(Sensor):\n",
+    "        sensor(data_item)\n",
+    "        \n",
+    "    datanode = data_item.getDataNode()\n",
+    "    datanode.inferILPConstrains(create, destroy, other, non_existence, known_loc, unknown_loc, fun=None)\n",
+    "    final_output = {\"id\": datanode.getAttribute('id'), \"entities\": data_item['entities'], \"steps\":[], \"actions\":[], \"steps_before\": [], \"actions_before\": []}\n",
+    "    for step_info in datanode.findDatanodes(select = step):\n",
+    "        k = step_info.getAttribute(known_loc, \"ILP\")\n",
+    "        u = step_info.getAttribute(unknown_loc, \"ILP\")\n",
+    "        n = step_info.getAttribute(non_existence, \"ILP\")\n",
+    "        final_output[\"steps\"].append((k,u,n))\n",
+    "        k = step_info.getAttribute(known_loc)\n",
+    "        u = step_info.getAttribute(unknown_loc)\n",
+    "        n = step_info.getAttribute(non_existence)\n",
+    "        final_output[\"steps_before\"].append((k,u,n))\n",
+    "    for action_info in datanode.findDatanodes(select = action):\n",
+    "        c = action_info.getAttribute(create, \"ILP\")\n",
+    "        d = action_info.getAttribute(destroy, \"ILP\")\n",
+    "        o = action_info.getAttribute(other, \"ILP\")\n",
+    "        final_output[\"actions\"].append((c,d,o))\n",
+    "        c = action_info.getAttribute(create)\n",
+    "        d = action_info.getAttribute(destroy)\n",
+    "        o = action_info.getAttribute(other)\n",
+    "        final_output[\"actions_before\"].append((c,d,o))\n",
+    "    all_updates.append(final_output)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 47,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'id': '37',\n",
+       " 'entities': ['bones'],\n",
+       " 'steps': [(tensor([1.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([0.], device='cuda:0', dtype=torch.float64),\n",
+       "   tensor([1.], device='cuda:0', dtype=torch.float64))],\n",
+       " 'actions': [(tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(1., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(1., device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(1., device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(1., device='cuda:0', dtype=torch.float64)),\n",
+       "  (tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(0., device='cuda:0', dtype=torch.float64),\n",
+       "   tensor(1., device='cuda:0', dtype=torch.float64))],\n",
+       " 'steps_before': [(tensor([0.0317, 0.9683]),\n",
+       "   tensor([0.9707, 0.0293]),\n",
+       "   tensor([0.9976, 0.0024])),\n",
+       "  (tensor([0.9877, 0.0123]),\n",
+       "   tensor([0.9972, 0.0028]),\n",
+       "   tensor([0.0150, 0.9850])),\n",
+       "  (tensor([0.9939, 0.0061]),\n",
+       "   tensor([0.9976, 0.0024]),\n",
+       "   tensor([0.0084, 0.9916])),\n",
+       "  (tensor([0.9976, 0.0024]),\n",
+       "   tensor([0.9978, 0.0022]),\n",
+       "   tensor([0.0046, 0.9954])),\n",
+       "  (tensor([0.9984, 0.0016]),\n",
+       "   tensor([0.9974, 0.0026]),\n",
+       "   tensor([0.0042, 0.9958])),\n",
+       "  (tensor([0.9985, 0.0015]),\n",
+       "   tensor([0.9972, 0.0028]),\n",
+       "   tensor([0.0043, 0.9957])),\n",
+       "  (tensor([0.9984, 0.0016]),\n",
+       "   tensor([0.9968, 0.0032]),\n",
+       "   tensor([0.0049, 0.9951]))],\n",
+       " 'actions_before': [(tensor([9.9996e-01, 3.6113e-05]),\n",
+       "   tensor([0.0174, 0.9826]),\n",
+       "   tensor([0.9826, 0.0174])),\n",
+       "  (tensor([0.9917, 0.0083]),\n",
+       "   tensor([0.9851, 0.0149]),\n",
+       "   tensor([0.0232, 0.9768])),\n",
+       "  (tensor([0.9954, 0.0046]),\n",
+       "   tensor([0.9916, 0.0084]),\n",
+       "   tensor([0.0130, 0.9870])),\n",
+       "  (tensor([0.9958, 0.0042]),\n",
+       "   tensor([0.9954, 0.0046]),\n",
+       "   tensor([0.0088, 0.9912])),\n",
+       "  (tensor([0.9957, 0.0043]),\n",
+       "   tensor([0.9958, 0.0042]),\n",
+       "   tensor([0.0085, 0.9915])),\n",
+       "  (tensor([0.9952, 0.0048]),\n",
+       "   tensor([0.9957, 0.0043]),\n",
+       "   tensor([0.0092, 0.9908]))]}"
+      ]
+     },
+     "execution_count": 47,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "all_updates[2]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 59,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "tensor([[0.9976, 0.0024],\n",
+      "        [0.0150, 0.9850],\n",
+      "        [0.0084, 0.9916],\n",
+      "        [0.0046, 0.9954],\n",
+      "        [0.0042, 0.9958],\n",
+      "        [0.0043, 0.9957],\n",
+      "        [0.0049, 0.9951]])\n",
+      "tensor([[0.0086, 0.9914],\n",
+      "        [0.0048, 0.9952],\n",
+      "        [0.0089, 0.9911],\n",
+      "        [0.9970, 0.0030],\n",
+      "        [0.9941, 0.0059],\n",
+      "        [0.0137, 0.9863],\n",
+      "        [0.0157, 0.9843]])\n",
+      "tensor([0.0150, 0.9850])\n"
+     ]
+    }
+   ],
+   "source": [
     "context = {\"graph\": graph}\n",
-    "context.update(next(iter(dataset)))\n",
+    "context.update(list(iter(dataset))[2])\n",
     "data_item = DataNodeBuilder(context)\n",
     "# data_item\n",
     "for sensor in procedure['id'].find(Sensor):\n",
@@ -659,6 +920,7 @@
     "for sensor in step[unknown_loc].find(Sensor):\n",
     "    sensor(data_item)\n",
     "for sensor in step[non_existence].find(Sensor):\n",
+    "    sensor1 = sensor\n",
     "    sensor(data_item)\n",
     "for sensor in action[action_arg1.backward].find(Sensor):\n",
     "    sensor(data_item)\n",
@@ -669,12 +931,16 @@
     "for sensor in action[create].find(Sensor):\n",
     "    sensor(data_item)\n",
     "for sensor in action[other].find(Sensor):\n",
-    "    sensor(data_item)"
+    "    sensor(data_item)\n",
+    "print(data_item[sensor1])\n",
+    "print(list(iter(dataset))[2]['non_existence'])\n",
+    "datanode = data_item.getDataNode()\n",
+    "print(datanode.findDatanodes(select=step)[1].getAttribute('non_existence'))"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 56,
    "metadata": {},
    "outputs": [
     {
@@ -794,39 +1060,39 @@
        "          [0., 0., 0., 0., 1., 0., 0.],\n",
        "          [0., 0., 0., 0., 0., 1., 0.],\n",
        "          [0., 0., 0., 0., 0., 0., 1.]])),\n",
-       " 'create': tensor([[9.9996e-01, 3.6113e-05],\n",
-       "         [9.9169e-01, 8.3119e-03],\n",
-       "         [9.9540e-01, 4.5985e-03],\n",
-       "         [9.9581e-01, 4.1883e-03],\n",
-       "         [9.9567e-01, 4.3308e-03],\n",
-       "         [9.9516e-01, 4.8409e-03]]),\n",
-       " 'destroy': tensor([[0.0174, 0.9826],\n",
-       "         [0.9851, 0.0149],\n",
-       "         [0.9916, 0.0084],\n",
-       "         [0.9954, 0.0046],\n",
-       "         [0.9958, 0.0042],\n",
-       "         [0.9957, 0.0043]]),\n",
-       " 'entities': ['plant; animal'],\n",
-       " 'known': tensor([[0.0317, 0.9683],\n",
-       "         [0.9877, 0.0123],\n",
-       "         [0.9939, 0.0061],\n",
-       "         [0.9976, 0.0024],\n",
-       "         [0.9984, 0.0016],\n",
-       "         [0.9985, 0.0015],\n",
-       "         [0.9984, 0.0016]]),\n",
-       " 'non_existence': tensor([[0.9976, 0.0024],\n",
-       "         [0.0150, 0.9850],\n",
-       "         [0.0084, 0.9916],\n",
-       "         [0.0046, 0.9954],\n",
-       "         [0.0042, 0.9958],\n",
-       "         [0.0043, 0.9957],\n",
-       "         [0.0049, 0.9951]]),\n",
-       " 'other': tensor([[0.9826, 0.0174],\n",
-       "         [0.0232, 0.9768],\n",
-       "         [0.0130, 0.9870],\n",
-       "         [0.0088, 0.9912],\n",
-       "         [0.0085, 0.9915],\n",
-       "         [0.0092, 0.9908]]),\n",
+       " 'create': tensor([[9.9527e-01, 4.7288e-03],\n",
+       "         [9.9112e-01, 8.8800e-03],\n",
+       "         [1.1862e-02, 9.8814e-01],\n",
+       "         [9.9705e-01, 2.9484e-03],\n",
+       "         [9.9992e-01, 8.1156e-05],\n",
+       "         [9.8447e-01, 1.5530e-02]]),\n",
+       " 'destroy': tensor([[9.9143e-01, 8.5749e-03],\n",
+       "         [9.9527e-01, 4.7273e-03],\n",
+       "         [9.9997e-01, 2.6464e-05],\n",
+       "         [9.9409e-01, 5.9068e-03],\n",
+       "         [1.9542e-02, 9.8046e-01],\n",
+       "         [9.8652e-01, 1.3483e-02]]),\n",
+       " 'entities': ['bones'],\n",
+       " 'known': tensor([[0.9949, 0.0051],\n",
+       "         [0.9977, 0.0023],\n",
+       "         [0.9951, 0.0049],\n",
+       "         [0.0568, 0.9432],\n",
+       "         [0.0971, 0.9029],\n",
+       "         [0.9950, 0.0050],\n",
+       "         [0.9935, 0.0065]]),\n",
+       " 'non_existence': tensor([[0.0086, 0.9914],\n",
+       "         [0.0048, 0.9952],\n",
+       "         [0.0089, 0.9911],\n",
+       "         [0.9970, 0.0030],\n",
+       "         [0.9941, 0.0059],\n",
+       "         [0.0137, 0.9863],\n",
+       "         [0.0157, 0.9843]]),\n",
+       " 'other': tensor([[0.0133, 0.9867],\n",
+       "         [0.0136, 0.9864],\n",
+       "         [0.9882, 0.0118],\n",
+       "         [0.0089, 0.9911],\n",
+       "         [0.9805, 0.0195],\n",
+       "         [0.0290, 0.9710]]),\n",
        " 'procedureID': ['37'],\n",
        " 'steps': (tensor([[1.],\n",
        "          [1.],\n",
@@ -842,15 +1108,15 @@
        "   'Over time sediment builds over the top and hardens into rock.',\n",
        "   'As the bone decays mineral seeps in replacing it.',\n",
        "   'Fossils are formed.']),\n",
-       " 'unkown': tensor([[0.9707, 0.0293],\n",
-       "         [0.9972, 0.0028],\n",
+       " 'unknown': tensor([[0.9965, 0.0035],\n",
        "         [0.9976, 0.0024],\n",
-       "         [0.9978, 0.0022],\n",
-       "         [0.9974, 0.0026],\n",
-       "         [0.9972, 0.0028],\n",
-       "         [0.9968, 0.0032]]),\n",
-       " 'Counter_setitem': 35,\n",
-       " 'Counter/global/procedure/id/readersensor': {('37',): {'counter': 1,\n",
+       "         [0.9960, 0.0040],\n",
+       "         [0.9462, 0.0538],\n",
+       "         [0.9088, 0.0912],\n",
+       "         [0.9913, 0.0087],\n",
+       "         [0.9908, 0.0092]]),\n",
+       " 'Counter_setitem': 200,\n",
+       " 'Counter/global/procedure/id/readersensor-75': {('37',): {'counter': 1,\n",
        "   'recent': True}},\n",
        " 'dataNode': [procedure 0,\n",
        "  action 0,\n",
@@ -858,30 +1124,21 @@
        "  action 2,\n",
        "  action 3,\n",
        "  action 4,\n",
-       "  action 5,\n",
-       "  action 0,\n",
-       "  action 1,\n",
-       "  action 2,\n",
-       "  action 3,\n",
-       "  action 4,\n",
-       "  action 5,\n",
-       "  action 0,\n",
-       "  action 1,\n",
-       "  action 2,\n",
-       "  action 3,\n",
-       "  action 4,\n",
-       "  action 5,\n",
-       "  action 0,\n",
-       "  action 1,\n",
-       "  action 2,\n",
-       "  action 3,\n",
-       "  action 4,\n",
        "  action 5],\n",
        " 'global/procedure/index': [procedure 0],\n",
-       " ReaderSensor(name='readersensor', fullname='global/procedure/id/readersensor'): ['37'],\n",
-       " 'DataNodeTime': 0.009783744812011719,\n",
-       " Property(name='id', fullname='global/procedure/id'): ['37'],\n",
-       " JoinReaderSensor(name='joinreadersensor', fullname='global/step/(Contains(name='procedure-contains-0-step', fullname='global/procedure-contains-0-step').forward, 'text')/joinreadersensor'): (tensor([[1.],\n",
+       " ReaderSensor(name='readersensor-75', fullname='global/procedure/id/readersensor-75'): ['37'],\n",
+       " 'DataNodeTime': 0.02908039093017578,\n",
+       " Property(name='id-3', fullname='global/procedure/id'): ['37'],\n",
+       " 'Counter/global/procedure/id/readersensor-90': {('37',): {'counter': 1,\n",
+       "   'recent': True}},\n",
+       " ReaderSensor(name='readersensor-90', fullname='global/procedure/id/readersensor-90'): ['37'],\n",
+       " 'Counter/global/procedure/id/readersensor-105': {('37',): {'counter': 1,\n",
+       "   'recent': True}},\n",
+       " ReaderSensor(name='readersensor-105', fullname='global/procedure/id/readersensor-105'): ['37'],\n",
+       " 'Counter/global/procedure/id/readersensor-120': {('37',): {'counter': 1,\n",
+       "   'recent': True}},\n",
+       " ReaderSensor(name='readersensor-120', fullname='global/procedure/id/readersensor-120'): ['37'],\n",
+       " JoinReaderSensor(name='joinreadersensor-15', fullname='global/step/(Contains(name='procedure-contains-0-step', fullname='global/procedure-contains-0-step').forward, 'text')/joinreadersensor-15'): (tensor([[1.],\n",
        "          [1.],\n",
        "          [1.],\n",
        "          [1.],\n",
@@ -895,7 +1152,7 @@
        "   'Over time sediment builds over the top and hardens into rock.',\n",
        "   'As the bone decays mineral seeps in replacing it.',\n",
        "   'Fossils are formed.']),\n",
-       " 'Counter/global/step/text/functionalsensor': {('step 0 information',\n",
+       " 'Counter/global/step/text/functionalsensor-5': {('step 0 information',\n",
        "   'A plant of animal dies in a watery environment.',\n",
        "   'Is buried in mud and silt.',\n",
        "   'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
@@ -909,28 +1166,112 @@
        "   step 4,\n",
        "   step 5,\n",
        "   step 6]],\n",
-       " FunctionalSensor(name='functionalsensor', fullname='global/step/text/functionalsensor'): ['step 0 information',\n",
+       " FunctionalSensor(name='functionalsensor-5', fullname='global/step/text/functionalsensor-5'): ['step 0 information',\n",
+       "  'A plant of animal dies in a watery environment.',\n",
+       "  'Is buried in mud and silt.',\n",
+       "  'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
+       "  'Over time sediment builds over the top and hardens into rock.',\n",
+       "  'As the bone decays mineral seeps in replacing it.',\n",
+       "  'Fossils are formed.'],\n",
+       " Property(name='text-3', fullname='global/step/text'): ['step 0 information',\n",
+       "  'A plant of animal dies in a watery environment.',\n",
+       "  'Is buried in mud and silt.',\n",
+       "  'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
+       "  'Over time sediment builds over the top and hardens into rock.',\n",
+       "  'As the bone decays mineral seeps in replacing it.',\n",
+       "  'Fossils are formed.'],\n",
+       " JoinReaderSensor(name='joinreadersensor-18', fullname='global/step/(Contains(name='procedure-contains-0-step', fullname='global/procedure-contains-0-step').forward, 'text')/joinreadersensor-18'): (tensor([[1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.]]),\n",
+       "  ['step 0 information',\n",
+       "   'A plant of animal dies in a watery environment.',\n",
+       "   'Is buried in mud and silt.',\n",
+       "   'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
+       "   'Over time sediment builds over the top and hardens into rock.',\n",
+       "   'As the bone decays mineral seeps in replacing it.',\n",
+       "   'Fossils are formed.']),\n",
+       " 'Counter/global/step/text/functionalsensor-6': {('step 0 information',\n",
+       "   'A plant of animal dies in a watery environment.',\n",
+       "   'Is buried in mud and silt.',\n",
+       "   'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
+       "   'Over time sediment builds over the top and hardens into rock.',\n",
+       "   'As the bone decays mineral seeps in replacing it.',\n",
+       "   'Fossils are formed.'): {'counter': 1, 'recent': True}},\n",
+       " FunctionalSensor(name='functionalsensor-6', fullname='global/step/text/functionalsensor-6'): ['step 0 information',\n",
+       "  'A plant of animal dies in a watery environment.',\n",
+       "  'Is buried in mud and silt.',\n",
+       "  'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
+       "  'Over time sediment builds over the top and hardens into rock.',\n",
+       "  'As the bone decays mineral seeps in replacing it.',\n",
+       "  'Fossils are formed.'],\n",
+       " JoinReaderSensor(name='joinreadersensor-21', fullname='global/step/(Contains(name='procedure-contains-0-step', fullname='global/procedure-contains-0-step').forward, 'text')/joinreadersensor-21'): (tensor([[1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.]]),\n",
+       "  ['step 0 information',\n",
+       "   'A plant of animal dies in a watery environment.',\n",
+       "   'Is buried in mud and silt.',\n",
+       "   'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
+       "   'Over time sediment builds over the top and hardens into rock.',\n",
+       "   'As the bone decays mineral seeps in replacing it.',\n",
+       "   'Fossils are formed.']),\n",
+       " 'Counter/global/step/text/functionalsensor-7': {('step 0 information',\n",
+       "   'A plant of animal dies in a watery environment.',\n",
+       "   'Is buried in mud and silt.',\n",
+       "   'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
+       "   'Over time sediment builds over the top and hardens into rock.',\n",
+       "   'As the bone decays mineral seeps in replacing it.',\n",
+       "   'Fossils are formed.'): {'counter': 1, 'recent': True}},\n",
+       " FunctionalSensor(name='functionalsensor-7', fullname='global/step/text/functionalsensor-7'): ['step 0 information',\n",
        "  'A plant of animal dies in a watery environment.',\n",
        "  'Is buried in mud and silt.',\n",
        "  'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
        "  'Over time sediment builds over the top and hardens into rock.',\n",
        "  'As the bone decays mineral seeps in replacing it.',\n",
        "  'Fossils are formed.'],\n",
-       " Property(name='text', fullname='global/step/text'): ['step 0 information',\n",
+       " JoinReaderSensor(name='joinreadersensor-24', fullname='global/step/(Contains(name='procedure-contains-0-step', fullname='global/procedure-contains-0-step').forward, 'text')/joinreadersensor-24'): (tensor([[1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.]]),\n",
+       "  ['step 0 information',\n",
+       "   'A plant of animal dies in a watery environment.',\n",
+       "   'Is buried in mud and silt.',\n",
+       "   'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
+       "   'Over time sediment builds over the top and hardens into rock.',\n",
+       "   'As the bone decays mineral seeps in replacing it.',\n",
+       "   'Fossils are formed.']),\n",
+       " 'Counter/global/step/text/functionalsensor-8': {('step 0 information',\n",
+       "   'A plant of animal dies in a watery environment.',\n",
+       "   'Is buried in mud and silt.',\n",
+       "   'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
+       "   'Over time sediment builds over the top and hardens into rock.',\n",
+       "   'As the bone decays mineral seeps in replacing it.',\n",
+       "   'Fossils are formed.'): {'counter': 1, 'recent': True}},\n",
+       " FunctionalSensor(name='functionalsensor-8', fullname='global/step/text/functionalsensor-8'): ['step 0 information',\n",
        "  'A plant of animal dies in a watery environment.',\n",
        "  'Is buried in mud and silt.',\n",
        "  'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
        "  'Over time sediment builds over the top and hardens into rock.',\n",
        "  'As the bone decays mineral seeps in replacing it.',\n",
        "  'Fossils are formed.'],\n",
-       " 'Counter/global/step/procedure-contains-0-step.forward/edgesensor': {tensor([[1.],\n",
+       " 'Counter/global/step/procedure-contains-0-step.forward/edgesensor-25': {tensor([[1.],\n",
        "          [1.],\n",
        "          [1.],\n",
        "          [1.],\n",
        "          [1.],\n",
        "          [1.],\n",
        "          [1.]]): {'counter': 1, 'recent': True}},\n",
-       " EdgeSensor(name='edgesensor', fullname='global/step/procedure-contains-0-step.forward/edgesensor'): tensor([[1.],\n",
+       " EdgeSensor(name='edgesensor-25', fullname='global/step/procedure-contains-0-step.forward/edgesensor-25'): tensor([[1.],\n",
        "         [1.],\n",
        "         [1.],\n",
        "         [1.],\n",
@@ -944,84 +1285,294 @@
        "         [1.],\n",
        "         [1.],\n",
        "         [1.]]),\n",
-       " 'Counter/global/step/<known_location>/readersensor-3': {tensor([[0.9707, 0.0293],\n",
-       "          [0.9972, 0.0028],\n",
-       "          [0.9976, 0.0024],\n",
-       "          [0.9978, 0.0022],\n",
-       "          [0.9974, 0.0026],\n",
-       "          [0.9972, 0.0028],\n",
-       "          [0.9968, 0.0032]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-3', fullname='global/step/<known_location>/readersensor-3'): tensor([[0.9707, 0.0293],\n",
-       "         [0.9972, 0.0028],\n",
-       "         [0.9976, 0.0024],\n",
-       "         [0.9978, 0.0022],\n",
-       "         [0.9974, 0.0026],\n",
-       "         [0.9972, 0.0028],\n",
-       "         [0.9968, 0.0032]]),\n",
-       " Property(name='known_location', fullname='global/step/<known_location>'): tensor([[0.9707, 0.0293],\n",
-       "         [0.9972, 0.0028],\n",
-       "         [0.9976, 0.0024],\n",
-       "         [0.9978, 0.0022],\n",
-       "         [0.9974, 0.0026],\n",
-       "         [0.9972, 0.0028],\n",
-       "         [0.9968, 0.0032]]),\n",
-       " 'Counter/global/step/<known_location>/readersensor-6': {tensor([[0.9707, 0.0293],\n",
-       "          [0.9972, 0.0028],\n",
+       " 'Counter/global/step/procedure-contains-0-step.forward/edgesensor-30': {tensor([[1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.]]): {'counter': 1, 'recent': True}},\n",
+       " EdgeSensor(name='edgesensor-30', fullname='global/step/procedure-contains-0-step.forward/edgesensor-30'): tensor([[1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.]]),\n",
+       " 'Counter/global/step/procedure-contains-0-step.forward/edgesensor-35': {tensor([[1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.]]): {'counter': 1, 'recent': True}},\n",
+       " EdgeSensor(name='edgesensor-35', fullname='global/step/procedure-contains-0-step.forward/edgesensor-35'): tensor([[1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.]]),\n",
+       " 'Counter/global/step/procedure-contains-0-step.forward/edgesensor-40': {tensor([[1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.]]): {'counter': 1, 'recent': True}},\n",
+       " EdgeSensor(name='edgesensor-40', fullname='global/step/procedure-contains-0-step.forward/edgesensor-40'): tensor([[1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.],\n",
+       "         [1.]]),\n",
+       " 'Counter/global/step/<known_location>/readersensor-78': {tensor([[0.0317, 0.9683],\n",
+       "          [0.9877, 0.0123],\n",
+       "          [0.9939, 0.0061],\n",
        "          [0.9976, 0.0024],\n",
-       "          [0.9978, 0.0022],\n",
-       "          [0.9974, 0.0026],\n",
-       "          [0.9972, 0.0028],\n",
-       "          [0.9968, 0.0032]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-6', fullname='global/step/<known_location>/readersensor-6'): tensor([[0.9707, 0.0293],\n",
-       "         [0.9972, 0.0028],\n",
+       "          [0.9984, 0.0016],\n",
+       "          [0.9985, 0.0015],\n",
+       "          [0.9984, 0.0016]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-78', fullname='global/step/<known_location>/readersensor-78'): tensor([[0.0317, 0.9683],\n",
+       "         [0.9877, 0.0123],\n",
+       "         [0.9939, 0.0061],\n",
        "         [0.9976, 0.0024],\n",
-       "         [0.9978, 0.0022],\n",
-       "         [0.9974, 0.0026],\n",
-       "         [0.9972, 0.0028],\n",
-       "         [0.9968, 0.0032]]),\n",
-       " 'Counter/global/step/<unknown_location>/readersensor-2': {tensor([[0.0317, 0.9683],\n",
+       "         [0.9984, 0.0016],\n",
+       "         [0.9985, 0.0015],\n",
+       "         [0.9984, 0.0016]]),\n",
+       " Property(name='known_location', fullname='global/step/<known_location>'): tensor([[0.0317, 0.9683],\n",
+       "         [0.9877, 0.0123],\n",
+       "         [0.9939, 0.0061],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9984, 0.0016],\n",
+       "         [0.9985, 0.0015],\n",
+       "         [0.9984, 0.0016]]),\n",
+       " 'Counter/global/step/<known_location>/readersensor-81': {tensor([[0.0317, 0.9683],\n",
+       "          [0.9877, 0.0123],\n",
+       "          [0.9939, 0.0061],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9984, 0.0016],\n",
+       "          [0.9985, 0.0015],\n",
+       "          [0.9984, 0.0016]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-81', fullname='global/step/<known_location>/readersensor-81'): tensor([[0.0317, 0.9683],\n",
+       "         [0.9877, 0.0123],\n",
+       "         [0.9939, 0.0061],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9984, 0.0016],\n",
+       "         [0.9985, 0.0015],\n",
+       "         [0.9984, 0.0016]]),\n",
+       " 'Counter/global/step/<known_location>/readersensor-93': {tensor([[0.0317, 0.9683],\n",
+       "          [0.9877, 0.0123],\n",
+       "          [0.9939, 0.0061],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9984, 0.0016],\n",
+       "          [0.9985, 0.0015],\n",
+       "          [0.9984, 0.0016]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-93', fullname='global/step/<known_location>/readersensor-93'): tensor([[0.0317, 0.9683],\n",
+       "         [0.9877, 0.0123],\n",
+       "         [0.9939, 0.0061],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9984, 0.0016],\n",
+       "         [0.9985, 0.0015],\n",
+       "         [0.9984, 0.0016]]),\n",
+       " 'Counter/global/step/<known_location>/readersensor-96': {tensor([[0.0317, 0.9683],\n",
+       "          [0.9877, 0.0123],\n",
+       "          [0.9939, 0.0061],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9984, 0.0016],\n",
+       "          [0.9985, 0.0015],\n",
+       "          [0.9984, 0.0016]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-96', fullname='global/step/<known_location>/readersensor-96'): tensor([[0.0317, 0.9683],\n",
+       "         [0.9877, 0.0123],\n",
+       "         [0.9939, 0.0061],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9984, 0.0016],\n",
+       "         [0.9985, 0.0015],\n",
+       "         [0.9984, 0.0016]]),\n",
+       " 'Counter/global/step/<known_location>/readersensor-108': {tensor([[0.0317, 0.9683],\n",
+       "          [0.9877, 0.0123],\n",
+       "          [0.9939, 0.0061],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9984, 0.0016],\n",
+       "          [0.9985, 0.0015],\n",
+       "          [0.9984, 0.0016]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-108', fullname='global/step/<known_location>/readersensor-108'): tensor([[0.0317, 0.9683],\n",
+       "         [0.9877, 0.0123],\n",
+       "         [0.9939, 0.0061],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9984, 0.0016],\n",
+       "         [0.9985, 0.0015],\n",
+       "         [0.9984, 0.0016]]),\n",
+       " 'Counter/global/step/<known_location>/readersensor-111': {tensor([[0.0317, 0.9683],\n",
        "          [0.9877, 0.0123],\n",
        "          [0.9939, 0.0061],\n",
        "          [0.9976, 0.0024],\n",
        "          [0.9984, 0.0016],\n",
        "          [0.9985, 0.0015],\n",
        "          [0.9984, 0.0016]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-2', fullname='global/step/<unknown_location>/readersensor-2'): tensor([[0.0317, 0.9683],\n",
+       " ReaderSensor(name='readersensor-111', fullname='global/step/<known_location>/readersensor-111'): tensor([[0.0317, 0.9683],\n",
        "         [0.9877, 0.0123],\n",
        "         [0.9939, 0.0061],\n",
        "         [0.9976, 0.0024],\n",
        "         [0.9984, 0.0016],\n",
        "         [0.9985, 0.0015],\n",
        "         [0.9984, 0.0016]]),\n",
-       " Property(name='unknown_location', fullname='global/step/<unknown_location>'): tensor([[0.0317, 0.9683],\n",
+       " 'Counter/global/step/<known_location>/readersensor-123': {tensor([[0.0317, 0.9683],\n",
+       "          [0.9877, 0.0123],\n",
+       "          [0.9939, 0.0061],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9984, 0.0016],\n",
+       "          [0.9985, 0.0015],\n",
+       "          [0.9984, 0.0016]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-123', fullname='global/step/<known_location>/readersensor-123'): tensor([[0.0317, 0.9683],\n",
        "         [0.9877, 0.0123],\n",
        "         [0.9939, 0.0061],\n",
        "         [0.9976, 0.0024],\n",
        "         [0.9984, 0.0016],\n",
        "         [0.9985, 0.0015],\n",
        "         [0.9984, 0.0016]]),\n",
-       " 'Counter/global/step/<unknown_location>/readersensor-5': {tensor([[0.0317, 0.9683],\n",
+       " 'Counter/global/step/<known_location>/readersensor-126': {tensor([[0.0317, 0.9683],\n",
        "          [0.9877, 0.0123],\n",
        "          [0.9939, 0.0061],\n",
        "          [0.9976, 0.0024],\n",
        "          [0.9984, 0.0016],\n",
        "          [0.9985, 0.0015],\n",
        "          [0.9984, 0.0016]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-5', fullname='global/step/<unknown_location>/readersensor-5'): tensor([[0.0317, 0.9683],\n",
+       " ReaderSensor(name='readersensor-126', fullname='global/step/<known_location>/readersensor-126'): tensor([[0.0317, 0.9683],\n",
        "         [0.9877, 0.0123],\n",
        "         [0.9939, 0.0061],\n",
        "         [0.9976, 0.0024],\n",
        "         [0.9984, 0.0016],\n",
        "         [0.9985, 0.0015],\n",
        "         [0.9984, 0.0016]]),\n",
-       " 'Counter/global/step/<non_existence>/readersensor-1': {tensor([[0.9976, 0.0024],\n",
+       " 'Counter/global/step/<unknown_location>/readersensor-77': {tensor([[0.9707, 0.0293],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9978, 0.0022],\n",
+       "          [0.9974, 0.0026],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9968, 0.0032]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-77', fullname='global/step/<unknown_location>/readersensor-77'): tensor([[0.9707, 0.0293],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9978, 0.0022],\n",
+       "         [0.9974, 0.0026],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9968, 0.0032]]),\n",
+       " Property(name='unknown_location', fullname='global/step/<unknown_location>'): tensor([[0.9707, 0.0293],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9978, 0.0022],\n",
+       "         [0.9974, 0.0026],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9968, 0.0032]]),\n",
+       " 'Counter/global/step/<unknown_location>/readersensor-80': {tensor([[0.9707, 0.0293],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9978, 0.0022],\n",
+       "          [0.9974, 0.0026],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9968, 0.0032]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-80', fullname='global/step/<unknown_location>/readersensor-80'): tensor([[0.9707, 0.0293],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9978, 0.0022],\n",
+       "         [0.9974, 0.0026],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9968, 0.0032]]),\n",
+       " 'Counter/global/step/<unknown_location>/readersensor-92': {tensor([[0.9707, 0.0293],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9978, 0.0022],\n",
+       "          [0.9974, 0.0026],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9968, 0.0032]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-92', fullname='global/step/<unknown_location>/readersensor-92'): tensor([[0.9707, 0.0293],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9978, 0.0022],\n",
+       "         [0.9974, 0.0026],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9968, 0.0032]]),\n",
+       " 'Counter/global/step/<unknown_location>/readersensor-95': {tensor([[0.9707, 0.0293],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9978, 0.0022],\n",
+       "          [0.9974, 0.0026],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9968, 0.0032]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-95', fullname='global/step/<unknown_location>/readersensor-95'): tensor([[0.9707, 0.0293],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9978, 0.0022],\n",
+       "         [0.9974, 0.0026],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9968, 0.0032]]),\n",
+       " 'Counter/global/step/<unknown_location>/readersensor-107': {tensor([[0.9707, 0.0293],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9978, 0.0022],\n",
+       "          [0.9974, 0.0026],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9968, 0.0032]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-107', fullname='global/step/<unknown_location>/readersensor-107'): tensor([[0.9707, 0.0293],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9978, 0.0022],\n",
+       "         [0.9974, 0.0026],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9968, 0.0032]]),\n",
+       " 'Counter/global/step/<unknown_location>/readersensor-110': {tensor([[0.9707, 0.0293],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9978, 0.0022],\n",
+       "          [0.9974, 0.0026],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9968, 0.0032]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-110', fullname='global/step/<unknown_location>/readersensor-110'): tensor([[0.9707, 0.0293],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9978, 0.0022],\n",
+       "         [0.9974, 0.0026],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9968, 0.0032]]),\n",
+       " 'Counter/global/step/<unknown_location>/readersensor-122': {tensor([[0.9707, 0.0293],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9978, 0.0022],\n",
+       "          [0.9974, 0.0026],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9968, 0.0032]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-122', fullname='global/step/<unknown_location>/readersensor-122'): tensor([[0.9707, 0.0293],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9978, 0.0022],\n",
+       "         [0.9974, 0.0026],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9968, 0.0032]]),\n",
+       " 'Counter/global/step/<unknown_location>/readersensor-125': {tensor([[0.9707, 0.0293],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9976, 0.0024],\n",
+       "          [0.9978, 0.0022],\n",
+       "          [0.9974, 0.0026],\n",
+       "          [0.9972, 0.0028],\n",
+       "          [0.9968, 0.0032]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-125', fullname='global/step/<unknown_location>/readersensor-125'): tensor([[0.9707, 0.0293],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9978, 0.0022],\n",
+       "         [0.9974, 0.0026],\n",
+       "         [0.9972, 0.0028],\n",
+       "         [0.9968, 0.0032]]),\n",
+       " 'Counter/global/step/<non_existence>/readersensor-76': {tensor([[0.9976, 0.0024],\n",
        "          [0.0150, 0.9850],\n",
        "          [0.0084, 0.9916],\n",
        "          [0.0046, 0.9954],\n",
        "          [0.0042, 0.9958],\n",
        "          [0.0043, 0.9957],\n",
        "          [0.0049, 0.9951]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-1', fullname='global/step/<non_existence>/readersensor-1'): tensor([[0.9976, 0.0024],\n",
+       " ReaderSensor(name='readersensor-76', fullname='global/step/<non_existence>/readersensor-76'): tensor([[0.9976, 0.0024],\n",
        "         [0.0150, 0.9850],\n",
        "         [0.0084, 0.9916],\n",
        "         [0.0046, 0.9954],\n",
@@ -1035,58 +1586,142 @@
        "         [0.0042, 0.9958],\n",
        "         [0.0043, 0.9957],\n",
        "         [0.0049, 0.9951]]),\n",
-       " 'Counter/global/step/<non_existence>/readersensor-4': {tensor([[0.9976, 0.0024],\n",
+       " 'Counter/global/step/<non_existence>/readersensor-79': {tensor([[0.9976, 0.0024],\n",
        "          [0.0150, 0.9850],\n",
        "          [0.0084, 0.9916],\n",
        "          [0.0046, 0.9954],\n",
        "          [0.0042, 0.9958],\n",
        "          [0.0043, 0.9957],\n",
        "          [0.0049, 0.9951]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-4', fullname='global/step/<non_existence>/readersensor-4'): tensor([[0.9976, 0.0024],\n",
+       " ReaderSensor(name='readersensor-79', fullname='global/step/<non_existence>/readersensor-79'): tensor([[0.9976, 0.0024],\n",
        "         [0.0150, 0.9850],\n",
        "         [0.0084, 0.9916],\n",
        "         [0.0046, 0.9954],\n",
        "         [0.0042, 0.9958],\n",
        "         [0.0043, 0.9957],\n",
        "         [0.0049, 0.9951]]),\n",
-       " JoinReaderSensor(name='joinreadersensor-1', fullname='global/action/(HasA(name='arg1-1', fullname='global/arg1-1').backward, HasA(name='arg2-1', fullname='global/arg2-1').backward)/joinreadersensor-1'): (tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
-       "          [0., 1., 0., 0., 0., 0., 0.],\n",
-       "          [0., 0., 1., 0., 0., 0., 0.],\n",
-       "          [0., 0., 0., 1., 0., 0., 0.],\n",
-       "          [0., 0., 0., 0., 1., 0., 0.],\n",
-       "          [0., 0., 0., 0., 0., 1., 0.]]),\n",
-       "  tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
-       "          [0., 0., 1., 0., 0., 0., 0.],\n",
-       "          [0., 0., 0., 1., 0., 0., 0.],\n",
-       "          [0., 0., 0., 0., 1., 0., 0.],\n",
-       "          [0., 0., 0., 0., 0., 1., 0.],\n",
-       "          [0., 0., 0., 0., 0., 0., 1.]])),\n",
-       " 'Counter/global/action/arg1-1.backward/edgesensor-1': {tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
-       "          [0., 1., 0., 0., 0., 0., 0.],\n",
-       "          [0., 0., 1., 0., 0., 0., 0.],\n",
-       "          [0., 0., 0., 1., 0., 0., 0.],\n",
-       "          [0., 0., 0., 0., 1., 0., 0.],\n",
-       "          [0., 0., 0., 0., 0., 1., 0.]]): {'counter': 1, 'recent': True}},\n",
-       " 'global/action/index': [action 0,\n",
-       "  action 1,\n",
-       "  action 2,\n",
-       "  action 3,\n",
-       "  action 4,\n",
-       "  action 5],\n",
-       " 'actionRelationAttrsCache': {'arg1-1': tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
-       "          [0., 1., 0., 0., 0., 0., 0.],\n",
-       "          [0., 0., 1., 0., 0., 0., 0.],\n",
-       "          [0., 0., 0., 1., 0., 0., 0.],\n",
-       "          [0., 0., 0., 0., 1., 0., 0.],\n",
-       "          [0., 0., 0., 0., 0., 1., 0.]]),\n",
-       "  'arg2-1': tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
-       "          [0., 0., 1., 0., 0., 0., 0.],\n",
-       "          [0., 0., 0., 1., 0., 0., 0.],\n",
-       "          [0., 0., 0., 0., 1., 0., 0.],\n",
-       "          [0., 0., 0., 0., 0., 1., 0.],\n",
-       "          [0., 0., 0., 0., 0., 0., 1.]])},\n",
-       " EdgeSensor(name='edgesensor-1', fullname='global/action/arg1-1.backward/edgesensor-1'): tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
-       "         [0., 1., 0., 0., 0., 0., 0.],\n",
+       " 'Counter/global/step/<non_existence>/readersensor-91': {tensor([[0.9976, 0.0024],\n",
+       "          [0.0150, 0.9850],\n",
+       "          [0.0084, 0.9916],\n",
+       "          [0.0046, 0.9954],\n",
+       "          [0.0042, 0.9958],\n",
+       "          [0.0043, 0.9957],\n",
+       "          [0.0049, 0.9951]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-91', fullname='global/step/<non_existence>/readersensor-91'): tensor([[0.9976, 0.0024],\n",
+       "         [0.0150, 0.9850],\n",
+       "         [0.0084, 0.9916],\n",
+       "         [0.0046, 0.9954],\n",
+       "         [0.0042, 0.9958],\n",
+       "         [0.0043, 0.9957],\n",
+       "         [0.0049, 0.9951]]),\n",
+       " 'Counter/global/step/<non_existence>/readersensor-94': {tensor([[0.9976, 0.0024],\n",
+       "          [0.0150, 0.9850],\n",
+       "          [0.0084, 0.9916],\n",
+       "          [0.0046, 0.9954],\n",
+       "          [0.0042, 0.9958],\n",
+       "          [0.0043, 0.9957],\n",
+       "          [0.0049, 0.9951]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-94', fullname='global/step/<non_existence>/readersensor-94'): tensor([[0.9976, 0.0024],\n",
+       "         [0.0150, 0.9850],\n",
+       "         [0.0084, 0.9916],\n",
+       "         [0.0046, 0.9954],\n",
+       "         [0.0042, 0.9958],\n",
+       "         [0.0043, 0.9957],\n",
+       "         [0.0049, 0.9951]]),\n",
+       " 'Counter/global/step/<non_existence>/readersensor-106': {tensor([[0.9976, 0.0024],\n",
+       "          [0.0150, 0.9850],\n",
+       "          [0.0084, 0.9916],\n",
+       "          [0.0046, 0.9954],\n",
+       "          [0.0042, 0.9958],\n",
+       "          [0.0043, 0.9957],\n",
+       "          [0.0049, 0.9951]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-106', fullname='global/step/<non_existence>/readersensor-106'): tensor([[0.9976, 0.0024],\n",
+       "         [0.0150, 0.9850],\n",
+       "         [0.0084, 0.9916],\n",
+       "         [0.0046, 0.9954],\n",
+       "         [0.0042, 0.9958],\n",
+       "         [0.0043, 0.9957],\n",
+       "         [0.0049, 0.9951]]),\n",
+       " 'Counter/global/step/<non_existence>/readersensor-109': {tensor([[0.9976, 0.0024],\n",
+       "          [0.0150, 0.9850],\n",
+       "          [0.0084, 0.9916],\n",
+       "          [0.0046, 0.9954],\n",
+       "          [0.0042, 0.9958],\n",
+       "          [0.0043, 0.9957],\n",
+       "          [0.0049, 0.9951]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-109', fullname='global/step/<non_existence>/readersensor-109'): tensor([[0.9976, 0.0024],\n",
+       "         [0.0150, 0.9850],\n",
+       "         [0.0084, 0.9916],\n",
+       "         [0.0046, 0.9954],\n",
+       "         [0.0042, 0.9958],\n",
+       "         [0.0043, 0.9957],\n",
+       "         [0.0049, 0.9951]]),\n",
+       " 'Counter/global/step/<non_existence>/readersensor-121': {tensor([[0.9976, 0.0024],\n",
+       "          [0.0150, 0.9850],\n",
+       "          [0.0084, 0.9916],\n",
+       "          [0.0046, 0.9954],\n",
+       "          [0.0042, 0.9958],\n",
+       "          [0.0043, 0.9957],\n",
+       "          [0.0049, 0.9951]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-121', fullname='global/step/<non_existence>/readersensor-121'): tensor([[0.9976, 0.0024],\n",
+       "         [0.0150, 0.9850],\n",
+       "         [0.0084, 0.9916],\n",
+       "         [0.0046, 0.9954],\n",
+       "         [0.0042, 0.9958],\n",
+       "         [0.0043, 0.9957],\n",
+       "         [0.0049, 0.9951]]),\n",
+       " 'Counter/global/step/<non_existence>/readersensor-124': {tensor([[0.9976, 0.0024],\n",
+       "          [0.0150, 0.9850],\n",
+       "          [0.0084, 0.9916],\n",
+       "          [0.0046, 0.9954],\n",
+       "          [0.0042, 0.9958],\n",
+       "          [0.0043, 0.9957],\n",
+       "          [0.0049, 0.9951]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-124', fullname='global/step/<non_existence>/readersensor-124'): tensor([[0.9976, 0.0024],\n",
+       "         [0.0150, 0.9850],\n",
+       "         [0.0084, 0.9916],\n",
+       "         [0.0046, 0.9954],\n",
+       "         [0.0042, 0.9958],\n",
+       "         [0.0043, 0.9957],\n",
+       "         [0.0049, 0.9951]]),\n",
+       " JoinReaderSensor(name='joinreadersensor-16', fullname='global/action/(HasA(name='arg1-1', fullname='global/arg1-1').backward, HasA(name='arg2-1', fullname='global/arg2-1').backward)/joinreadersensor-16'): (tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.]]),\n",
+       "  tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.]])),\n",
+       " 'Counter/global/action/arg1-1.backward/edgesensor-26': {tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.]]): {'counter': 1, 'recent': True}},\n",
+       " 'global/action/index': [action 0,\n",
+       "  action 1,\n",
+       "  action 2,\n",
+       "  action 3,\n",
+       "  action 4,\n",
+       "  action 5],\n",
+       " 'actionRelationAttrsCache': {'arg1-1': tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.]]),\n",
+       "  'arg2-1': tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.]])},\n",
+       " EdgeSensor(name='edgesensor-26', fullname='global/action/arg1-1.backward/edgesensor-26'): tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "         [0., 1., 0., 0., 0., 0., 0.],\n",
        "         [0., 0., 1., 0., 0., 0., 0.],\n",
        "         [0., 0., 0., 1., 0., 0., 0.],\n",
        "         [0., 0., 0., 0., 1., 0., 0.],\n",
@@ -1097,13 +1732,85 @@
        "         [0., 0., 0., 1., 0., 0., 0.],\n",
        "         [0., 0., 0., 0., 1., 0., 0.],\n",
        "         [0., 0., 0., 0., 0., 1., 0.]]),\n",
-       " 'Counter/global/action/arg2-1.backward/edgesensor-2': {tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       " JoinReaderSensor(name='joinreadersensor-19', fullname='global/action/(HasA(name='arg1-1', fullname='global/arg1-1').backward, HasA(name='arg2-1', fullname='global/arg2-1').backward)/joinreadersensor-19'): (tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.]]),\n",
+       "  tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.]])),\n",
+       " 'Counter/global/action/arg1-1.backward/edgesensor-31': {tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.]]): {'counter': 1, 'recent': True}},\n",
+       " EdgeSensor(name='edgesensor-31', fullname='global/action/arg1-1.backward/edgesensor-31'): tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "         [0., 1., 0., 0., 0., 0., 0.],\n",
+       "         [0., 0., 1., 0., 0., 0., 0.],\n",
+       "         [0., 0., 0., 1., 0., 0., 0.],\n",
+       "         [0., 0., 0., 0., 1., 0., 0.],\n",
+       "         [0., 0., 0., 0., 0., 1., 0.]]),\n",
+       " JoinReaderSensor(name='joinreadersensor-22', fullname='global/action/(HasA(name='arg1-1', fullname='global/arg1-1').backward, HasA(name='arg2-1', fullname='global/arg2-1').backward)/joinreadersensor-22'): (tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.]]),\n",
+       "  tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.]])),\n",
+       " 'Counter/global/action/arg1-1.backward/edgesensor-36': {tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.]]): {'counter': 1, 'recent': True}},\n",
+       " EdgeSensor(name='edgesensor-36', fullname='global/action/arg1-1.backward/edgesensor-36'): tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "         [0., 1., 0., 0., 0., 0., 0.],\n",
+       "         [0., 0., 1., 0., 0., 0., 0.],\n",
+       "         [0., 0., 0., 1., 0., 0., 0.],\n",
+       "         [0., 0., 0., 0., 1., 0., 0.],\n",
+       "         [0., 0., 0., 0., 0., 1., 0.]]),\n",
+       " JoinReaderSensor(name='joinreadersensor-25', fullname='global/action/(HasA(name='arg1-1', fullname='global/arg1-1').backward, HasA(name='arg2-1', fullname='global/arg2-1').backward)/joinreadersensor-25'): (tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.]]),\n",
+       "  tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.]])),\n",
+       " 'Counter/global/action/arg1-1.backward/edgesensor-41': {tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.]]): {'counter': 1, 'recent': True}},\n",
+       " EdgeSensor(name='edgesensor-41', fullname='global/action/arg1-1.backward/edgesensor-41'): tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "         [0., 1., 0., 0., 0., 0., 0.],\n",
+       "         [0., 0., 1., 0., 0., 0., 0.],\n",
+       "         [0., 0., 0., 1., 0., 0., 0.],\n",
+       "         [0., 0., 0., 0., 1., 0., 0.],\n",
+       "         [0., 0., 0., 0., 0., 1., 0.]]),\n",
+       " 'Counter/global/action/arg2-1.backward/edgesensor-27': {tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
        "          [0., 0., 1., 0., 0., 0., 0.],\n",
        "          [0., 0., 0., 1., 0., 0., 0.],\n",
        "          [0., 0., 0., 0., 1., 0., 0.],\n",
        "          [0., 0., 0., 0., 0., 1., 0.],\n",
        "          [0., 0., 0., 0., 0., 0., 1.]]): {'counter': 1, 'recent': True}},\n",
-       " EdgeSensor(name='edgesensor-2', fullname='global/action/arg2-1.backward/edgesensor-2'): tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       " EdgeSensor(name='edgesensor-27', fullname='global/action/arg2-1.backward/edgesensor-27'): tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
        "         [0., 0., 1., 0., 0., 0., 0.],\n",
        "         [0., 0., 0., 1., 0., 0., 0.],\n",
        "         [0., 0., 0., 0., 1., 0., 0.],\n",
@@ -1115,13 +1822,49 @@
        "         [0., 0., 0., 0., 1., 0., 0.],\n",
        "         [0., 0., 0., 0., 0., 1., 0.],\n",
        "         [0., 0., 0., 0., 0., 0., 1.]]),\n",
-       " 'Counter/global/action/<destroy>/readersensor-8': {tensor([[0.0174, 0.9826],\n",
+       " 'Counter/global/action/arg2-1.backward/edgesensor-32': {tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.]]): {'counter': 1, 'recent': True}},\n",
+       " EdgeSensor(name='edgesensor-32', fullname='global/action/arg2-1.backward/edgesensor-32'): tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "         [0., 0., 1., 0., 0., 0., 0.],\n",
+       "         [0., 0., 0., 1., 0., 0., 0.],\n",
+       "         [0., 0., 0., 0., 1., 0., 0.],\n",
+       "         [0., 0., 0., 0., 0., 1., 0.],\n",
+       "         [0., 0., 0., 0., 0., 0., 1.]]),\n",
+       " 'Counter/global/action/arg2-1.backward/edgesensor-37': {tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.]]): {'counter': 1, 'recent': True}},\n",
+       " EdgeSensor(name='edgesensor-37', fullname='global/action/arg2-1.backward/edgesensor-37'): tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "         [0., 0., 1., 0., 0., 0., 0.],\n",
+       "         [0., 0., 0., 1., 0., 0., 0.],\n",
+       "         [0., 0., 0., 0., 1., 0., 0.],\n",
+       "         [0., 0., 0., 0., 0., 1., 0.],\n",
+       "         [0., 0., 0., 0., 0., 0., 1.]]),\n",
+       " 'Counter/global/action/arg2-1.backward/edgesensor-42': {tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.]]): {'counter': 1, 'recent': True}},\n",
+       " EdgeSensor(name='edgesensor-42', fullname='global/action/arg2-1.backward/edgesensor-42'): tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "         [0., 0., 1., 0., 0., 0., 0.],\n",
+       "         [0., 0., 0., 1., 0., 0., 0.],\n",
+       "         [0., 0., 0., 0., 1., 0., 0.],\n",
+       "         [0., 0., 0., 0., 0., 1., 0.],\n",
+       "         [0., 0., 0., 0., 0., 0., 1.]]),\n",
+       " 'Counter/global/action/<destroy>/readersensor-83': {tensor([[0.0174, 0.9826],\n",
        "          [0.9851, 0.0149],\n",
        "          [0.9916, 0.0084],\n",
        "          [0.9954, 0.0046],\n",
        "          [0.9958, 0.0042],\n",
        "          [0.9957, 0.0043]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-8', fullname='global/action/<destroy>/readersensor-8'): tensor([[0.0174, 0.9826],\n",
+       " ReaderSensor(name='readersensor-83', fullname='global/action/<destroy>/readersensor-83'): tensor([[0.0174, 0.9826],\n",
        "         [0.9851, 0.0149],\n",
        "         [0.9916, 0.0084],\n",
        "         [0.9954, 0.0046],\n",
@@ -1133,215 +1876,491 @@
        "         [0.9954, 0.0046],\n",
        "         [0.9958, 0.0042],\n",
        "         [0.9957, 0.0043]]),\n",
-       " 'Counter/global/action/<destroy>/readersensor-11': {tensor([[0.0174, 0.9826],\n",
+       " 'Counter/global/action/<destroy>/readersensor-86': {tensor([[0.0174, 0.9826],\n",
        "          [0.9851, 0.0149],\n",
        "          [0.9916, 0.0084],\n",
        "          [0.9954, 0.0046],\n",
        "          [0.9958, 0.0042],\n",
        "          [0.9957, 0.0043]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-11', fullname='global/action/<destroy>/readersensor-11'): tensor([[0.0174, 0.9826],\n",
+       " ReaderSensor(name='readersensor-86', fullname='global/action/<destroy>/readersensor-86'): tensor([[0.0174, 0.9826],\n",
        "         [0.9851, 0.0149],\n",
        "         [0.9916, 0.0084],\n",
        "         [0.9954, 0.0046],\n",
        "         [0.9958, 0.0042],\n",
        "         [0.9957, 0.0043]]),\n",
-       " 'Counter/global/action/<create>/readersensor-7': {tensor([[9.9996e-01, 3.6113e-05],\n",
-       "          [9.9169e-01, 8.3119e-03],\n",
-       "          [9.9540e-01, 4.5985e-03],\n",
-       "          [9.9581e-01, 4.1883e-03],\n",
-       "          [9.9567e-01, 4.3308e-03],\n",
-       "          [9.9516e-01, 4.8409e-03]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-7', fullname='global/action/<create>/readersensor-7'): tensor([[9.9996e-01, 3.6113e-05],\n",
-       "         [9.9169e-01, 8.3119e-03],\n",
-       "         [9.9540e-01, 4.5985e-03],\n",
-       "         [9.9581e-01, 4.1883e-03],\n",
-       "         [9.9567e-01, 4.3308e-03],\n",
-       "         [9.9516e-01, 4.8409e-03]]),\n",
-       " Property(name='create', fullname='global/action/<create>'): tensor([[9.9996e-01, 3.6113e-05],\n",
-       "         [9.9169e-01, 8.3119e-03],\n",
-       "         [9.9540e-01, 4.5985e-03],\n",
-       "         [9.9581e-01, 4.1883e-03],\n",
-       "         [9.9567e-01, 4.3308e-03],\n",
-       "         [9.9516e-01, 4.8409e-03]]),\n",
-       " 'Counter/global/action/<create>/readersensor-10': {tensor([[9.9996e-01, 3.6113e-05],\n",
-       "          [9.9169e-01, 8.3119e-03],\n",
-       "          [9.9540e-01, 4.5985e-03],\n",
-       "          [9.9581e-01, 4.1883e-03],\n",
-       "          [9.9567e-01, 4.3308e-03],\n",
-       "          [9.9516e-01, 4.8409e-03]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-10', fullname='global/action/<create>/readersensor-10'): tensor([[9.9996e-01, 3.6113e-05],\n",
-       "         [9.9169e-01, 8.3119e-03],\n",
-       "         [9.9540e-01, 4.5985e-03],\n",
-       "         [9.9581e-01, 4.1883e-03],\n",
-       "         [9.9567e-01, 4.3308e-03],\n",
-       "         [9.9516e-01, 4.8409e-03]]),\n",
-       " 'Counter/global/action/<other>/readersensor-9': {tensor([[0.9826, 0.0174],\n",
-       "          [0.0232, 0.9768],\n",
-       "          [0.0130, 0.9870],\n",
-       "          [0.0088, 0.9912],\n",
-       "          [0.0085, 0.9915],\n",
-       "          [0.0092, 0.9908]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-9', fullname='global/action/<other>/readersensor-9'): tensor([[0.9826, 0.0174],\n",
-       "         [0.0232, 0.9768],\n",
-       "         [0.0130, 0.9870],\n",
-       "         [0.0088, 0.9912],\n",
-       "         [0.0085, 0.9915],\n",
-       "         [0.0092, 0.9908]]),\n",
-       " Property(name='other', fullname='global/action/<other>'): tensor([[0.9826, 0.0174],\n",
-       "         [0.0232, 0.9768],\n",
-       "         [0.0130, 0.9870],\n",
-       "         [0.0088, 0.9912],\n",
-       "         [0.0085, 0.9915],\n",
-       "         [0.0092, 0.9908]]),\n",
-       " 'Counter/global/action/<other>/readersensor-12': {tensor([[0.9826, 0.0174],\n",
-       "          [0.0232, 0.9768],\n",
-       "          [0.0130, 0.9870],\n",
-       "          [0.0088, 0.9912],\n",
-       "          [0.0085, 0.9915],\n",
-       "          [0.0092, 0.9908]]): {'counter': 1, 'recent': True}},\n",
-       " ReaderSensor(name='readersensor-12', fullname='global/action/<other>/readersensor-12'): tensor([[0.9826, 0.0174],\n",
-       "         [0.0232, 0.9768],\n",
-       "         [0.0130, 0.9870],\n",
-       "         [0.0088, 0.9912],\n",
-       "         [0.0085, 0.9915],\n",
-       "         [0.0092, 0.9908]])}"
-      ]
-     },
-     "execution_count": 11,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "data_item"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 12,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "procedure 0\n"
-     ]
-    }
-   ],
-   "source": [
-    "datanode = data_item.getDataNode()\n",
-    "print(datanode)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 13,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "step 0 information\n",
-      "tensor([0.0317, 0.9683])\n",
-      "tensor([0.9707, 0.0293])\n",
-      "tensor([0.9976, 0.0024])\n"
-     ]
-    }
-   ],
-   "source": [
-    "data1 = datanode.findDatanodes(select = step)[0].getAttribute(\"text\")\n",
-    "print(data1)\n",
-    "data1 = datanode.findDatanodes(select = step)[0].getAttribute(unknown_loc)\n",
-    "print(data1)\n",
-    "data1 = datanode.findDatanodes(select = step)[0].getAttribute(known_loc)\n",
-    "print(data1)\n",
-    "data1 = datanode.findDatanodes(select = step)[0].getAttribute(non_existence)\n",
-    "print(data1)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 14,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "6\n",
-      "tensor([9.9996e-01, 3.6113e-05])\n",
-      "tensor([0.0174, 0.9826])\n",
-      "tensor([0.9826, 0.0174])\n"
-     ]
+       " 'Counter/global/action/<destroy>/readersensor-98': {tensor([[0.0174, 0.9826],\n",
+       "          [0.9851, 0.0149],\n",
+       "          [0.9916, 0.0084],\n",
+       "          [0.9954, 0.0046],\n",
+       "          [0.9958, 0.0042],\n",
+       "          [0.9957, 0.0043]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-98', fullname='global/action/<destroy>/readersensor-98'): tensor([[0.0174, 0.9826],\n",
+       "         [0.9851, 0.0149],\n",
+       "         [0.9916, 0.0084],\n",
+       "         [0.9954, 0.0046],\n",
+       "         [0.9958, 0.0042],\n",
+       "         [0.9957, 0.0043]]),\n",
+       " 'Counter/global/action/<destroy>/readersensor-101': {tensor([[0.0174, 0.9826],\n",
+       "          [0.9851, 0.0149],\n",
+       "          [0.9916, 0.0084],\n",
+       "          [0.9954, 0.0046],\n",
+       "          [0.9958, 0.0042],\n",
+       "          [0.9957, 0.0043]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-101', fullname='global/action/<destroy>/readersensor-101'): tensor([[0.0174, 0.9826],\n",
+       "         [0.9851, 0.0149],\n",
+       "         [0.9916, 0.0084],\n",
+       "         [0.9954, 0.0046],\n",
+       "         [0.9958, 0.0042],\n",
+       "         [0.9957, 0.0043]]),\n",
+       " 'Counter/global/action/<destroy>/readersensor-113': {tensor([[0.0174, 0.9826],\n",
+       "          [0.9851, 0.0149],\n",
+       "          [0.9916, 0.0084],\n",
+       "          [0.9954, 0.0046],\n",
+       "          [0.9958, 0.0042],\n",
+       "          [0.9957, 0.0043]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-113', fullname='global/action/<destroy>/readersensor-113'): tensor([[0.0174, 0.9826],\n",
+       "         [0.9851, 0.0149],\n",
+       "         [0.9916, 0.0084],\n",
+       "         [0.9954, 0.0046],\n",
+       "         [0.9958, 0.0042],\n",
+       "         [0.9957, 0.0043]]),\n",
+       " 'Counter/global/action/<destroy>/readersensor-116': {tensor([[0.0174, 0.9826],\n",
+       "          [0.9851, 0.0149],\n",
+       "          [0.9916, 0.0084],\n",
+       "          [0.9954, 0.0046],\n",
+       "          [0.9958, 0.0042],\n",
+       "          [0.9957, 0.0043]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-116', fullname='global/action/<destroy>/readersensor-116'): tensor([[0.0174, 0.9826],\n",
+       "         [0.9851, 0.0149],\n",
+       "         [0.9916, 0.0084],\n",
+       "         [0.9954, 0.0046],\n",
+       "         [0.9958, 0.0042],\n",
+       "         [0.9957, 0.0043]]),\n",
+       " 'Counter/global/action/<destroy>/readersensor-128': {tensor([[0.0174, 0.9826],\n",
+       "          [0.9851, 0.0149],\n",
+       "          [0.9916, 0.0084],\n",
+       "          [0.9954, 0.0046],\n",
+       "          [0.9958, 0.0042],\n",
+       "          [0.9957, 0.0043]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-128', fullname='global/action/<destroy>/readersensor-128'): tensor([[0.0174, 0.9826],\n",
+       "         [0.9851, 0.0149],\n",
+       "         [0.9916, 0.0084],\n",
+       "         [0.9954, 0.0046],\n",
+       "         [0.9958, 0.0042],\n",
+       "         [0.9957, 0.0043]]),\n",
+       " 'Counter/global/action/<destroy>/readersensor-131': {tensor([[0.0174, 0.9826],\n",
+       "          [0.9851, 0.0149],\n",
+       "          [0.9916, 0.0084],\n",
+       "          [0.9954, 0.0046],\n",
+       "          [0.9958, 0.0042],\n",
+       "          [0.9957, 0.0043]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-131', fullname='global/action/<destroy>/readersensor-131'): tensor([[0.0174, 0.9826],\n",
+       "         [0.9851, 0.0149],\n",
+       "         [0.9916, 0.0084],\n",
+       "         [0.9954, 0.0046],\n",
+       "         [0.9958, 0.0042],\n",
+       "         [0.9957, 0.0043]]),\n",
+       " 'Counter/global/action/<create>/readersensor-82': {tensor([[9.9996e-01, 3.6113e-05],\n",
+       "          [9.9169e-01, 8.3119e-03],\n",
+       "          [9.9540e-01, 4.5985e-03],\n",
+       "          [9.9581e-01, 4.1883e-03],\n",
+       "          [9.9567e-01, 4.3308e-03],\n",
+       "          [9.9516e-01, 4.8409e-03]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-82', fullname='global/action/<create>/readersensor-82'): tensor([[9.9996e-01, 3.6113e-05],\n",
+       "         [9.9169e-01, 8.3119e-03],\n",
+       "         [9.9540e-01, 4.5985e-03],\n",
+       "         [9.9581e-01, 4.1883e-03],\n",
+       "         [9.9567e-01, 4.3308e-03],\n",
+       "         [9.9516e-01, 4.8409e-03]]),\n",
+       " Property(name='create', fullname='global/action/<create>'): tensor([[9.9996e-01, 3.6113e-05],\n",
+       "         [9.9169e-01, 8.3119e-03],\n",
+       "         [9.9540e-01, 4.5985e-03],\n",
+       "         [9.9581e-01, 4.1883e-03],\n",
+       "         [9.9567e-01, 4.3308e-03],\n",
+       "         [9.9516e-01, 4.8409e-03]]),\n",
+       " 'Counter/global/action/<create>/readersensor-85': {tensor([[9.9996e-01, 3.6113e-05],\n",
+       "          [9.9169e-01, 8.3119e-03],\n",
+       "          [9.9540e-01, 4.5985e-03],\n",
+       "          [9.9581e-01, 4.1883e-03],\n",
+       "          [9.9567e-01, 4.3308e-03],\n",
+       "          [9.9516e-01, 4.8409e-03]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-85', fullname='global/action/<create>/readersensor-85'): tensor([[9.9996e-01, 3.6113e-05],\n",
+       "         [9.9169e-01, 8.3119e-03],\n",
+       "         [9.9540e-01, 4.5985e-03],\n",
+       "         [9.9581e-01, 4.1883e-03],\n",
+       "         [9.9567e-01, 4.3308e-03],\n",
+       "         [9.9516e-01, 4.8409e-03]]),\n",
+       " 'Counter/global/action/<create>/readersensor-97': {tensor([[9.9996e-01, 3.6113e-05],\n",
+       "          [9.9169e-01, 8.3119e-03],\n",
+       "          [9.9540e-01, 4.5985e-03],\n",
+       "          [9.9581e-01, 4.1883e-03],\n",
+       "          [9.9567e-01, 4.3308e-03],\n",
+       "          [9.9516e-01, 4.8409e-03]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-97', fullname='global/action/<create>/readersensor-97'): tensor([[9.9996e-01, 3.6113e-05],\n",
+       "         [9.9169e-01, 8.3119e-03],\n",
+       "         [9.9540e-01, 4.5985e-03],\n",
+       "         [9.9581e-01, 4.1883e-03],\n",
+       "         [9.9567e-01, 4.3308e-03],\n",
+       "         [9.9516e-01, 4.8409e-03]]),\n",
+       " 'Counter/global/action/<create>/readersensor-100': {tensor([[9.9996e-01, 3.6113e-05],\n",
+       "          [9.9169e-01, 8.3119e-03],\n",
+       "          [9.9540e-01, 4.5985e-03],\n",
+       "          [9.9581e-01, 4.1883e-03],\n",
+       "          [9.9567e-01, 4.3308e-03],\n",
+       "          [9.9516e-01, 4.8409e-03]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-100', fullname='global/action/<create>/readersensor-100'): tensor([[9.9996e-01, 3.6113e-05],\n",
+       "         [9.9169e-01, 8.3119e-03],\n",
+       "         [9.9540e-01, 4.5985e-03],\n",
+       "         [9.9581e-01, 4.1883e-03],\n",
+       "         [9.9567e-01, 4.3308e-03],\n",
+       "         [9.9516e-01, 4.8409e-03]]),\n",
+       " 'Counter/global/action/<create>/readersensor-112': {tensor([[9.9996e-01, 3.6113e-05],\n",
+       "          [9.9169e-01, 8.3119e-03],\n",
+       "          [9.9540e-01, 4.5985e-03],\n",
+       "          [9.9581e-01, 4.1883e-03],\n",
+       "          [9.9567e-01, 4.3308e-03],\n",
+       "          [9.9516e-01, 4.8409e-03]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-112', fullname='global/action/<create>/readersensor-112'): tensor([[9.9996e-01, 3.6113e-05],\n",
+       "         [9.9169e-01, 8.3119e-03],\n",
+       "         [9.9540e-01, 4.5985e-03],\n",
+       "         [9.9581e-01, 4.1883e-03],\n",
+       "         [9.9567e-01, 4.3308e-03],\n",
+       "         [9.9516e-01, 4.8409e-03]]),\n",
+       " 'Counter/global/action/<create>/readersensor-115': {tensor([[9.9996e-01, 3.6113e-05],\n",
+       "          [9.9169e-01, 8.3119e-03],\n",
+       "          [9.9540e-01, 4.5985e-03],\n",
+       "          [9.9581e-01, 4.1883e-03],\n",
+       "          [9.9567e-01, 4.3308e-03],\n",
+       "          [9.9516e-01, 4.8409e-03]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-115', fullname='global/action/<create>/readersensor-115'): tensor([[9.9996e-01, 3.6113e-05],\n",
+       "         [9.9169e-01, 8.3119e-03],\n",
+       "         [9.9540e-01, 4.5985e-03],\n",
+       "         [9.9581e-01, 4.1883e-03],\n",
+       "         [9.9567e-01, 4.3308e-03],\n",
+       "         [9.9516e-01, 4.8409e-03]]),\n",
+       " 'Counter/global/action/<create>/readersensor-127': {tensor([[9.9996e-01, 3.6113e-05],\n",
+       "          [9.9169e-01, 8.3119e-03],\n",
+       "          [9.9540e-01, 4.5985e-03],\n",
+       "          [9.9581e-01, 4.1883e-03],\n",
+       "          [9.9567e-01, 4.3308e-03],\n",
+       "          [9.9516e-01, 4.8409e-03]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-127', fullname='global/action/<create>/readersensor-127'): tensor([[9.9996e-01, 3.6113e-05],\n",
+       "         [9.9169e-01, 8.3119e-03],\n",
+       "         [9.9540e-01, 4.5985e-03],\n",
+       "         [9.9581e-01, 4.1883e-03],\n",
+       "         [9.9567e-01, 4.3308e-03],\n",
+       "         [9.9516e-01, 4.8409e-03]]),\n",
+       " 'Counter/global/action/<create>/readersensor-130': {tensor([[9.9996e-01, 3.6113e-05],\n",
+       "          [9.9169e-01, 8.3119e-03],\n",
+       "          [9.9540e-01, 4.5985e-03],\n",
+       "          [9.9581e-01, 4.1883e-03],\n",
+       "          [9.9567e-01, 4.3308e-03],\n",
+       "          [9.9516e-01, 4.8409e-03]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-130', fullname='global/action/<create>/readersensor-130'): tensor([[9.9996e-01, 3.6113e-05],\n",
+       "         [9.9169e-01, 8.3119e-03],\n",
+       "         [9.9540e-01, 4.5985e-03],\n",
+       "         [9.9581e-01, 4.1883e-03],\n",
+       "         [9.9567e-01, 4.3308e-03],\n",
+       "         [9.9516e-01, 4.8409e-03]]),\n",
+       " 'Counter/global/action/<other>/readersensor-84': {tensor([[0.9826, 0.0174],\n",
+       "          [0.0232, 0.9768],\n",
+       "          [0.0130, 0.9870],\n",
+       "          [0.0088, 0.9912],\n",
+       "          [0.0085, 0.9915],\n",
+       "          [0.0092, 0.9908]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-84', fullname='global/action/<other>/readersensor-84'): tensor([[0.9826, 0.0174],\n",
+       "         [0.0232, 0.9768],\n",
+       "         [0.0130, 0.9870],\n",
+       "         [0.0088, 0.9912],\n",
+       "         [0.0085, 0.9915],\n",
+       "         [0.0092, 0.9908]]),\n",
+       " Property(name='other', fullname='global/action/<other>'): tensor([[0.9826, 0.0174],\n",
+       "         [0.0232, 0.9768],\n",
+       "         [0.0130, 0.9870],\n",
+       "         [0.0088, 0.9912],\n",
+       "         [0.0085, 0.9915],\n",
+       "         [0.0092, 0.9908]]),\n",
+       " 'Counter/global/action/<other>/readersensor-87': {tensor([[0.9826, 0.0174],\n",
+       "          [0.0232, 0.9768],\n",
+       "          [0.0130, 0.9870],\n",
+       "          [0.0088, 0.9912],\n",
+       "          [0.0085, 0.9915],\n",
+       "          [0.0092, 0.9908]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-87', fullname='global/action/<other>/readersensor-87'): tensor([[0.9826, 0.0174],\n",
+       "         [0.0232, 0.9768],\n",
+       "         [0.0130, 0.9870],\n",
+       "         [0.0088, 0.9912],\n",
+       "         [0.0085, 0.9915],\n",
+       "         [0.0092, 0.9908]]),\n",
+       " 'Counter/global/action/<other>/readersensor-99': {tensor([[0.9826, 0.0174],\n",
+       "          [0.0232, 0.9768],\n",
+       "          [0.0130, 0.9870],\n",
+       "          [0.0088, 0.9912],\n",
+       "          [0.0085, 0.9915],\n",
+       "          [0.0092, 0.9908]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-99', fullname='global/action/<other>/readersensor-99'): tensor([[0.9826, 0.0174],\n",
+       "         [0.0232, 0.9768],\n",
+       "         [0.0130, 0.9870],\n",
+       "         [0.0088, 0.9912],\n",
+       "         [0.0085, 0.9915],\n",
+       "         [0.0092, 0.9908]]),\n",
+       " 'Counter/global/action/<other>/readersensor-102': {tensor([[0.9826, 0.0174],\n",
+       "          [0.0232, 0.9768],\n",
+       "          [0.0130, 0.9870],\n",
+       "          [0.0088, 0.9912],\n",
+       "          [0.0085, 0.9915],\n",
+       "          [0.0092, 0.9908]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-102', fullname='global/action/<other>/readersensor-102'): tensor([[0.9826, 0.0174],\n",
+       "         [0.0232, 0.9768],\n",
+       "         [0.0130, 0.9870],\n",
+       "         [0.0088, 0.9912],\n",
+       "         [0.0085, 0.9915],\n",
+       "         [0.0092, 0.9908]]),\n",
+       " 'Counter/global/action/<other>/readersensor-114': {tensor([[0.9826, 0.0174],\n",
+       "          [0.0232, 0.9768],\n",
+       "          [0.0130, 0.9870],\n",
+       "          [0.0088, 0.9912],\n",
+       "          [0.0085, 0.9915],\n",
+       "          [0.0092, 0.9908]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-114', fullname='global/action/<other>/readersensor-114'): tensor([[0.9826, 0.0174],\n",
+       "         [0.0232, 0.9768],\n",
+       "         [0.0130, 0.9870],\n",
+       "         [0.0088, 0.9912],\n",
+       "         [0.0085, 0.9915],\n",
+       "         [0.0092, 0.9908]]),\n",
+       " 'Counter/global/action/<other>/readersensor-117': {tensor([[0.9826, 0.0174],\n",
+       "          [0.0232, 0.9768],\n",
+       "          [0.0130, 0.9870],\n",
+       "          [0.0088, 0.9912],\n",
+       "          [0.0085, 0.9915],\n",
+       "          [0.0092, 0.9908]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-117', fullname='global/action/<other>/readersensor-117'): tensor([[0.9826, 0.0174],\n",
+       "         [0.0232, 0.9768],\n",
+       "         [0.0130, 0.9870],\n",
+       "         [0.0088, 0.9912],\n",
+       "         [0.0085, 0.9915],\n",
+       "         [0.0092, 0.9908]]),\n",
+       " 'Counter/global/action/<other>/readersensor-129': {tensor([[0.9826, 0.0174],\n",
+       "          [0.0232, 0.9768],\n",
+       "          [0.0130, 0.9870],\n",
+       "          [0.0088, 0.9912],\n",
+       "          [0.0085, 0.9915],\n",
+       "          [0.0092, 0.9908]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-129', fullname='global/action/<other>/readersensor-129'): tensor([[0.9826, 0.0174],\n",
+       "         [0.0232, 0.9768],\n",
+       "         [0.0130, 0.9870],\n",
+       "         [0.0088, 0.9912],\n",
+       "         [0.0085, 0.9915],\n",
+       "         [0.0092, 0.9908]]),\n",
+       " 'Counter/global/action/<other>/readersensor-132': {tensor([[0.9826, 0.0174],\n",
+       "          [0.0232, 0.9768],\n",
+       "          [0.0130, 0.9870],\n",
+       "          [0.0088, 0.9912],\n",
+       "          [0.0085, 0.9915],\n",
+       "          [0.0092, 0.9908]]): {'counter': 1, 'recent': True}},\n",
+       " ReaderSensor(name='readersensor-132', fullname='global/action/<other>/readersensor-132'): tensor([[0.9826, 0.0174],\n",
+       "         [0.0232, 0.9768],\n",
+       "         [0.0130, 0.9870],\n",
+       "         [0.0088, 0.9912],\n",
+       "         [0.0085, 0.9915],\n",
+       "         [0.0092, 0.9908]]),\n",
+       " 'CounterGetDataNode': 1}"
+      ]
+     },
+     "execution_count": 56,
+     "metadata": {},
+     "output_type": "execute_result"
     }
    ],
    "source": [
-    "data1 = len(datanode.findDatanodes(select = action))\n",
-    "print(data1)\n",
-    "data1 = datanode.findDatanodes(select = action)[0].getAttribute(create)\n",
-    "print(data1)\n",
-    "data1 = datanode.findDatanodes(select = action)[0].getAttribute(destroy)\n",
-    "print(data1)\n",
-    "data1 = datanode.findDatanodes(select = action)[0].getAttribute(other)\n",
-    "print(data1)"
+    "data_item"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 52,
    "metadata": {},
    "outputs": [
     {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Log file for ilpOntSolver is in: /home/hfaghihi/Framework/DomiKnowS/examples/Propara/ilpOntSolver.log\n",
-      "Using license file /home/hfaghihi/gurobi.lic\n"
-     ]
-    },
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "INFO:gurobipy:Using license file /home/hfaghihi/gurobi.lic\n"
-     ]
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Academic license - for non-commercial use only\n"
-     ]
-    },
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "INFO:gurobipy:Academic license - for non-commercial use only\n"
-     ]
-    },
-    {
-     "ename": "UnboundLocalError",
-     "evalue": "local variable 'ilpKey' referenced before assignment",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[0;31mUnboundLocalError\u001b[0m                         Traceback (most recent call last)",
-      "\u001b[0;32m<ipython-input-15-a35d1901c9e2>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mdatanode\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minferILPConstrains\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcreate\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdestroy\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnon_existence\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mknown_loc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0munknown_loc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfun\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
-      "\u001b[0;32m~/Framework/DomiKnowS/regr/graph/dataNode.py\u001b[0m in \u001b[0;36minferILPConstrains\u001b[0;34m(self, fun, epsilon, minimizeObjective, *_conceptsRelations)\u001b[0m\n\u001b[1;32m    979\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    980\u001b[0m         \u001b[0mtokenResult\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpairResult\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtripleResult\u001b[0m \u001b[0;34m=\u001b[0m\u001b[0;31m \u001b[0m\u001b[0;31m\\\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 981\u001b[0;31m             \u001b[0mmyilpOntSolver\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcalculateILPSelection\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minfer_candidatesID\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgraphResultsForPhraseToken\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgraphResultsForPhraseRelation\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgraphResultsForTripleRelations\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mminimizeObjective\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mminimizeObjective\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mhardConstrains\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhardConstrains\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    982\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    983\u001b[0m         \u001b[0mdevice\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'cuda'\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcuda\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_available\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0;34m'cpu'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/Framework/DomiKnowS/regr/solver/gurobiILPOntSolver.py\u001b[0m in \u001b[0;36mcalculateILPSelection\u001b[0;34m(self, phrase, graphResultsForPhraseToken, graphResultsForPhraseRelation, graphResultsForPhraseTripleRelation, minimizeObjective, hardConstrains)\u001b[0m\n\u001b[1;32m   1637\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1638\u001b[0m                 \u001b[0;31m# Add constraints to the copy model\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1639\u001b[0;31m                 \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__addLogicalConstrains\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlcs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmP\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mconcepts\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtokens\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mxP\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0myP\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mzP\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mhardConstrains\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mhardConstrains\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1640\u001b[0m                 \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmyLogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Optimizing model for logical constraints with probabilities %s with %i variables and %i constrains'\u001b[0m\u001b[0;34m%\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mps\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mmP\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNumVars\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mmP\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNumConstrs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1641\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/Framework/DomiKnowS/regr/solver/gurobiILPOntSolver.py\u001b[0m in \u001b[0;36m__addLogicalConstrains\u001b[0;34m(self, lcs, m, concepts, tokens, x, y, z, hardConstrains)\u001b[0m\n\u001b[1;32m   1153\u001b[0m         \u001b[0;32mfor\u001b[0m \u001b[0mlc\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mlcs\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1154\u001b[0m             \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmyLogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Processing Logical Constrain %s - %s - %s'\u001b[0m\u001b[0;34m%\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlcName\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0me\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0me\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mlc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0me\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1155\u001b[0;31m             \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__constructLogicalConstrains\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmyIlpBooleanProcessor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mm\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mconcepts\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtokens\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mz\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mhardConstrains\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mhardConstrains\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheadLC\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1156\u001b[0m             \u001b[0;32mif\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1157\u001b[0m                 \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmyLogger\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Successfully added Logical Constrain %s'\u001b[0m\u001b[0;34m%\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlcName\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/Framework/DomiKnowS/regr/solver/gurobiILPOntSolver.py\u001b[0m in \u001b[0;36m__constructLogicalConstrains\u001b[0;34m(self, lc, booleanProcesor, m, concepts, tokens, x, y, z, hardConstrains, resultVariableNames, headLC)\u001b[0m\n\u001b[1;32m   1325\u001b[0m                 \u001b[0;32mreturn\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1326\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1327\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mlc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mm\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbooleanProcesor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlcVariables\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mresultVariableNames\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mresultVariableNames\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheadConstrain\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mheadLC\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   1328\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   1329\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0m_typeOfConcept\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/Framework/DomiKnowS/regr/graph/logicalConstrain.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, model, myIlpBooleanProcessor, v, resultVariableNames, headConstrain)\u001b[0m\n\u001b[1;32m    349\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    350\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0m__call__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmyIlpBooleanProcessor\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mv\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mresultVariableNames\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheadConstrain\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 351\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreateILPConstrains\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Nand'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmyIlpBooleanProcessor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnandVar\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mv\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mresultVariableNames\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheadConstrain\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    352\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    353\u001b[0m \u001b[0;32mclass\u001b[0m \u001b[0mifL\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mLogicalConstrain\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;32m~/Framework/DomiKnowS/regr/graph/logicalConstrain.py\u001b[0m in \u001b[0;36mcreateILPConstrains\u001b[0;34m(self, lcName, lcFun, model, v, resultVariableNames, headConstrain)\u001b[0m\n\u001b[1;32m    263\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    264\u001b[0m         \u001b[0;31m# Output\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 265\u001b[0;31m         \u001b[0milpV\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0milpKey\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mzVars\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    266\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    267\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
-      "\u001b[0;31mUnboundLocalError\u001b[0m: local variable 'ilpKey' referenced before assignment"
-     ]
+     "data": {
+      "text/plain": [
+       "{'action': (tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.]]),\n",
+       "  tensor([[0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.]])),\n",
+       " 'before_true': tensor([0., 1., 1., 1., 1., 1., 1., 0., 0., 1., 1., 1., 1., 1., 0., 0., 0., 1.,\n",
+       "         1., 1., 1., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0.,\n",
+       "         0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0.]),\n",
+       " 'before': (tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.]]),\n",
+       "  tensor([[1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.],\n",
+       "          [1., 0., 0., 0., 0., 0., 0.],\n",
+       "          [0., 1., 0., 0., 0., 0., 0.],\n",
+       "          [0., 0., 1., 0., 0., 0., 0.],\n",
+       "          [0., 0., 0., 1., 0., 0., 0.],\n",
+       "          [0., 0., 0., 0., 1., 0., 0.],\n",
+       "          [0., 0., 0., 0., 0., 1., 0.],\n",
+       "          [0., 0., 0., 0., 0., 0., 1.]])),\n",
+       " 'create': tensor([[9.9527e-01, 4.7288e-03],\n",
+       "         [9.9112e-01, 8.8800e-03],\n",
+       "         [1.1862e-02, 9.8814e-01],\n",
+       "         [9.9705e-01, 2.9484e-03],\n",
+       "         [9.9992e-01, 8.1156e-05],\n",
+       "         [9.8447e-01, 1.5530e-02]]),\n",
+       " 'destroy': tensor([[9.9143e-01, 8.5749e-03],\n",
+       "         [9.9527e-01, 4.7273e-03],\n",
+       "         [9.9997e-01, 2.6464e-05],\n",
+       "         [9.9409e-01, 5.9068e-03],\n",
+       "         [1.9542e-02, 9.8046e-01],\n",
+       "         [9.8652e-01, 1.3483e-02]]),\n",
+       " 'entities': ['bones'],\n",
+       " 'known': tensor([[0.9949, 0.0051],\n",
+       "         [0.9977, 0.0023],\n",
+       "         [0.9951, 0.0049],\n",
+       "         [0.0568, 0.9432],\n",
+       "         [0.0971, 0.9029],\n",
+       "         [0.9950, 0.0050],\n",
+       "         [0.9935, 0.0065]]),\n",
+       " 'non_existence': tensor([[0.0086, 0.9914],\n",
+       "         [0.0048, 0.9952],\n",
+       "         [0.0089, 0.9911],\n",
+       "         [0.9970, 0.0030],\n",
+       "         [0.9941, 0.0059],\n",
+       "         [0.0137, 0.9863],\n",
+       "         [0.0157, 0.9843]]),\n",
+       " 'other': tensor([[0.0133, 0.9867],\n",
+       "         [0.0136, 0.9864],\n",
+       "         [0.9882, 0.0118],\n",
+       "         [0.0089, 0.9911],\n",
+       "         [0.9805, 0.0195],\n",
+       "         [0.0290, 0.9710]]),\n",
+       " 'procedureID': ['37'],\n",
+       " 'steps': (tensor([[1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.],\n",
+       "          [1.]]),\n",
+       "  ['step 0 information',\n",
+       "   'A plant of animal dies in a watery environment.',\n",
+       "   'Is buried in mud and silt.',\n",
+       "   'Soft tissues quickly decompose leaving behind hard bones or shells.',\n",
+       "   'Over time sediment builds over the top and hardens into rock.',\n",
+       "   'As the bone decays mineral seeps in replacing it.',\n",
+       "   'Fossils are formed.']),\n",
+       " 'unknown': tensor([[0.9965, 0.0035],\n",
+       "         [0.9976, 0.0024],\n",
+       "         [0.9960, 0.0040],\n",
+       "         [0.9462, 0.0538],\n",
+       "         [0.9088, 0.0912],\n",
+       "         [0.9913, 0.0087],\n",
+       "         [0.9908, 0.0092]])}"
+      ]
+     },
+     "execution_count": 52,
+     "metadata": {},
+     "output_type": "execute_result"
     }
    ],
    "source": [
-    "datanode.inferILPConstrains(create, destroy, other, non_existence, known_loc, unknown_loc, fun=None)"
+    "list(iter(dataset))[2]"
    ]
   },
   {
diff --git a/examples/Propara/ProparaILP.py b/examples/Propara/ProparaILP.py
index 497829900..67e0d1927 100644
--- a/examples/Propara/ProparaILP.py
+++ b/examples/Propara/ProparaILP.py
@@ -39,7 +39,7 @@ def getknownval(self, item):
             values.append([1 - item['entity_step'][step][0], item['entity_step'][step][0]])
         return torch.tensor(values)
     
-    def getunkownval(self, item):
+    def getunknownval(self, item):
         values = []
         for step in range(len(item['steps']) + 1):
             values.append([1 - item['entity_step'][step][1], item['entity_step'][step][1]])
@@ -133,6 +133,7 @@ def getbefore_trueval(self, item):
                 values[(step*num_steps)+step1] = 1
         return values
     
+    
 from regr.graph import Graph, Concept, Relation
 from regr.graph.logicalConstrain import orL, andL, existsL, notL, atLeastL, atMostL, ifL, nandL, eqL
 
@@ -224,12 +225,12 @@ def model_declaration():
 #     entity['raw'] = ReaderSensor(keyword='entities')
 
     step[non_existence] = ReaderSensor(procedure_contain_step.forward, 'text', keyword='non_existence')
-    step[unknown_loc] = ReaderSensor(procedure_contain_step.forward, 'text', keyword='known')
-    step[known_loc] = ReaderSensor(procedure_contain_step.forward, 'text', keyword='unkown')
+    step[unknown_loc] = ReaderSensor(procedure_contain_step.forward, 'text', keyword='unknown')
+    step[known_loc] = ReaderSensor(procedure_contain_step.forward, 'text', keyword='known')
     
     step[non_existence] = ReaderSensor(keyword='non_existence', label=True)
-    step[unknown_loc] = ReaderSensor(keyword='known', label=True)
-    step[known_loc] = ReaderSensor(keyword='unkown', label=True)
+    step[unknown_loc] = ReaderSensor(keyword='unknown', label=True)
+    step[known_loc] = ReaderSensor(keyword='known', label=True)
     
     action[action_arg1.backward, action_arg2.backward] = JoinReaderSensor(step['text'], keyword='action')
     
@@ -254,51 +255,30 @@ def model_declaration():
     })
     return program
 
-from regr.graph import DataNodeBuilder
-from regr.sensor.sensor import Sensor
-import logging
-logging.basicConfig(level=logging.INFO)
 
-lbp = model_declaration()
-dataset = ProparaReader(file='updated_test_data.json')
-for datanode in lbp.populate(list(dataset)[0:1], device="cpu"):
-    pass
-dataset = ProparaReader(file='updated_test_data.json')
-context = {"graph": graph}
-context.update(next(iter(dataset)))
-data_item = DataNodeBuilder(context)
-# data_item
-for sensor in procedure['id'].find(Sensor):
-    sensor(data_item)
-for sensor in step['text'].find(Sensor):
-    sensor(data_item)
-for sensor in step[procedure_contain_step.forward].find(Sensor):
-    sensor(data_item)
-for sensor in step[known_loc].find(Sensor):
-    sensor(data_item)
-for sensor in step[unknown_loc].find(Sensor):
-    sensor(data_item)
-for sensor in step[non_existence].find(Sensor):
-    sensor(data_item)
-for sensor in action[action_arg1.backward].find(Sensor):
-    sensor(data_item)
-for sensor in action[action_arg2.backward].find(Sensor):
-    sensor(data_item)
-for sensor in action[destroy].find(Sensor):
-    sensor(data_item)
-for sensor in action[create].find(Sensor):
-    sensor(data_item)
-for sensor in action[other].find(Sensor):
-    sensor(data_item)
-    
-datanode = data_item.getDataNode()
-# data1 = len(datanode.findDatanodes(select = action))
-# print(data1)
-# data1 = datanode.findDatanodes(select = action)[0].getAttribute(create)
-# print(data1)
-# data1 = datanode.findDatanodes(select = action)[0].getAttribute(destroy)
-# print(data1)
-# data1 = datanode.findDatanodes(select = action)[0].getAttribute(other)
-# print(data1)
+def main():
+    # set logger level to see training and testing logs
+    import logging
+    logging.basicConfig(level=logging.INFO)
+
+    lbp = model_declaration()
+
+    dataset = ProparaReader(file='updated_test_data.json')  # Adding the info on the reader
+
+#     lbp.test(dataset, device='auto')
+    for datanode in lbp.populate(dataset, device="cpu"):
+#         print('datanode:', datanode)
+        datanode.inferILPConstrains(create, destroy, other, non_existence, known_loc, unknown_loc, fun=None)
+        final_output = {"id": datanode.getAttribute('id'), "steps":[], "actions":[], "steps_before": [], "actions_before": []}
+        for step_info in datanode.findDatanodes(select = step):
+            k = step_info.getAttribute(known_loc, "ILP").item()
+            u = step_info.getAttribute(unknown_loc, "ILP").item()
+            n = step_info.getAttribute(non_existence, "ILP").item()
+            assert k + u + n == 1
+        for action_info in datanode.findDatanodes(select = action):
+            c = action_info.getAttribute(create, "ILP").item()
+            d = action_info.getAttribute(destroy, "ILP").item()
+            o = action_info.getAttribute(other, "ILP").item()
+            assert c + d + o == 1
 
-datanode.inferILPConstrains(create, destroy, other, non_existence, known_loc, unknown_loc, fun=None)
+main()
diff --git a/examples/SentimentAnalysis/Test.ipynb b/examples/SentimentAnalysis/Test.ipynb
index 696913ec4..4b402066a 100644
--- a/examples/SentimentAnalysis/Test.ipynb
+++ b/examples/SentimentAnalysis/Test.ipynb
@@ -145,8 +145,8 @@
    "outputs": [],
    "source": [
     "class ButDetector(DataNodeSensor):\n",
-    "    def forward(self, instance, *inputs) -> Any:\n",
-    "        if \" but \" in instance.getAttribute('index').lower():\n",
+    "    def forward(self, *inputs, datanode) -> Any:\n",
+    "        if \" but \" in datanode.getAttribute('index').lower():\n",
     "            return True\n",
     "        else:\n",
     "            return False\n",
@@ -157,8 +157,8 @@
     "\n",
     "\n",
     "class SpacyVector(DataNodeSensor):\n",
-    "    def forward(self, instance, *inputs):\n",
-    "        return instance.getAttribute('index').vector\n",
+    "    def forward(self, *inputs, datanode):\n",
+    "        return datanode.getAttribute('index').vector\n",
     "    \n",
     "class SentenceRepSensor(DataNodeSensor):\n",
     "    def __init__(self, *pres, edges=None, forward= None ,label=False, device='auto', spacy=None):\n",
@@ -168,8 +168,8 @@
     "        else:\n",
     "            raise ValueError(\"spacy should be instantiated\")\n",
     "            \n",
-    "    def forward(self, instance, *inputs) -> Any:\n",
-    "        text = self.nlp(instance.getAttribute(self.pres[0]))\n",
+    "    def forward(self, *inputs, datanode) -> Any:\n",
+    "        text = self.nlp(datanode.getAttribute(self.pres[0]))\n",
     "        return torch.from_numpy(text.vector).to(device=self.device)\n",
     "#         return text.vector"
    ]
@@ -302,4 +302,4 @@
  },
  "nbformat": 4,
  "nbformat_minor": 4
-}
+}
\ No newline at end of file
diff --git a/examples/demo/main.py b/examples/demo/main.py
index c3999c89d..9009044c5 100644
--- a/examples/demo/main.py
+++ b/examples/demo/main.py
@@ -67,7 +67,7 @@ def __len__(self):  # optional
 
 scw = sentence.relate_to(word)[0]
 word[scw.forward, 'text'] = JointSensor(sentence['text'], forward=tokenize)
-word['emb'] = ModuleLearner('text', module=WordEmbedding())
+word['emb'] = ModuleLearner('text', scw.forward, module=WordEmbedding())
 
 word[people] = ReaderSensor(keyword='peop', label=True)
 word[organization] = ReaderSensor(keyword='org', label=True)
diff --git a/examples/demo/models.py b/examples/demo/models.py
index e7ea0fef2..b6d82ab47 100644
--- a/examples/demo/models.py
+++ b/examples/demo/models.py
@@ -26,7 +26,7 @@ def ws2i(self, words):
         device = next(self.embeds.parameters()).device
         return torch.tensor(ids, device=device)
 
-    def forward(self, words):
+    def forward(self, words, _):
         ids = self.ws2i(words)
         return self.embeds(ids)
 
diff --git a/examples/simple-logic/main.py b/examples/simple-logic/main.py
index 79034b657..1ab80bd8a 100644
--- a/examples/simple-logic/main.py
+++ b/examples/simple-logic/main.py
@@ -1,7 +1,8 @@
 def model_declaration():
     import torch
     from regr.program import LearningBasedProgram, IMLProgram
-    from regr.sensor.pytorch.sensors import ConstantSensor, ReaderSensor, TorchEdgeReaderSensor
+    from regr.sensor.pytorch.sensors import ConstantSensor, ReaderSensor
+    from regr.sensor.pytorch.relation_sensors import EdgeSensor
     from regr.sensor.pytorch.learners import ModuleLearner
     from regr.graph import Property
     from regr.program.loss import BCEWithLogitsLoss, BCEWithLogitsIMLoss
@@ -18,13 +19,13 @@ def model_declaration():
     y0 = graph['y0']
     y1 = graph['y1']
 
-    world['index'] = ConstantSensor(data=[[0.]])
-    world_contains_x['forward'] = TorchEdgeReaderSensor(keyword='x', mode='forward', to='x')
+    world['x'] = ReaderSensor(keyword='x')
+    x[world_contains_x] = EdgeSensor(world['x'], relation=world_contains_x, forward=lambda x: x)
 
     x[y0] = ReaderSensor(keyword='y0', label=True)
     x[y1] = ReaderSensor(keyword='y1', label=True)
-    x[y0] = ModuleLearner('x', module=Net(), edges=[world_contains_x['forward']])
-    x[y1] = ModuleLearner('x', module=Net(), edges=[world_contains_x['forward']])
+    x[y0] = ModuleLearner(world_contains_x('x'), module=Net())
+    x[y1] = ModuleLearner(world_contains_x('x'), module=Net())
 
     # program = LearningBasedProgram(graph, MyIMLModel)
     program = IMLProgram(
@@ -48,9 +49,9 @@ def main():
 
     program = model_declaration()
     data = [{
-        'x': [1.],
-        'y0': [1.,0.],
-        'y1': [0.,1.]
+        'x': [[1.]],
+        'y0': [[1.,0.]],
+        'y1': [[0.,1.]]
         }]
     program.train(data, train_epoch_num=10, Optim=lambda param: torch.optim.SGD(param, lr=1), device='auto')
     print('Train loss:', program.model.loss)
diff --git a/examples/simple-logic/main1.py b/examples/simple-logic/main1.py
index 070d1e90a..9d8ef063a 100644
--- a/examples/simple-logic/main1.py
+++ b/examples/simple-logic/main1.py
@@ -1,7 +1,8 @@
 def model_declaration():
     import torch
     from regr.program import LearningBasedProgram
-    from regr.sensor.pytorch.sensors import ReaderSensor, TorchEdgeReaderSensor
+    from regr.sensor.pytorch.sensors import ReaderSensor
+    from regr.sensor.pytorch.relation_sensors import EdgeSensor
     from regr.sensor.pytorch.learners import ModuleLearner
     from regr.graph import Property
 
@@ -15,14 +16,13 @@ def model_declaration():
     y0 = graph['y0']
     y1 = graph['y1']
 
-    world['index'] = ReaderSensor(keyword='x')
-    world_contains_x['forward'] = TorchEdgeReaderSensor(keyword='x', mode='forward', to='x')
+    world['x'] = ReaderSensor(keyword='x')
+    x[world_contains_x] = EdgeSensor(world['x'], relation=world_contains_x, forward=lambda x: x)
 
-    # x['x'] = ReaderSensor(keyword='x')
     x[y0] = ReaderSensor(keyword='y0', label=True)
     x[y1] = ReaderSensor(keyword='y1', label=True)
-    x[y0] = ModuleLearner('x', module=Net(), edges=[world_contains_x['forward']])
-    x[y1] = ModuleLearner('x', module=Net(), edges=[world_contains_x['forward']])
+    x[y0] = ModuleLearner(world_contains_x('x'), module=Net())
+    x[y1] = ModuleLearner(world_contains_x('x'), module=Net())
 
     program = LearningBasedProgram(graph, MyIMLModel)
     return program
@@ -38,9 +38,9 @@ def main():
 
     program = model_declaration()
     data = [{
-        'x': torch.tensor([1.]),
-        'y0': torch.tensor([1.,0.]),
-        'y1': torch.tensor([0.,1.])
+        'x': torch.tensor([[1.]]),
+        'y0': torch.tensor([[1.,0.]]),
+        'y1': torch.tensor([[0.,1.]])
         }]
     program.train(data, train_epoch_num=10, Optim=lambda param: torch.optim.SGD(param, lr=1))
     print('Train loss:', program.model.loss)
diff --git a/examples/simple-logic/main_newmodel.py b/examples/simple-logic/main_newmodel.py
index d14b07900..eab97b828 100644
--- a/examples/simple-logic/main_newmodel.py
+++ b/examples/simple-logic/main_newmodel.py
@@ -3,7 +3,8 @@ def model_declaration():
     from regr.program import POILossProgram
     from regr.program.model.pytorch import PoiModelToWorkWithLearnerWithLoss
     from regr.program.loss import BCEWithLogitsLoss
-    from regr.sensor.pytorch.sensors import ConstantSensor, ReaderSensor, TorchEdgeReaderSensor
+    from regr.sensor.pytorch.sensors import ConstantSensor, ReaderSensor
+    from regr.sensor.pytorch.relation_sensors import EdgeSensor
     from regr.sensor.pytorch.learners import ModuleLearner
     from regr.graph import Property
 
@@ -17,13 +18,14 @@ def model_declaration():
     y0 = graph['y0']
     y1 = graph['y1']
 
-    world['index'] = ConstantSensor(data=[[0.]])
-    world_contains_x['forward'] = TorchEdgeReaderSensor(keyword='x', mode='forward', to='x')
+    world['x'] = ReaderSensor(keyword='x')
+    x[world_contains_x] = EdgeSensor(world['x'], relation=world_contains_x, forward=lambda x: x)
 
     x[y0] = ReaderSensor(keyword='y0', label=True)
     x[y1] = ReaderSensor(keyword='y1', label=True)
-    x[y0] = ModuleLearner('x', module=Net(), edges=[world_contains_x['forward']], loss=BCEWithLogitsLoss())
-    x[y1] = ModuleLearner('x', module=Net(), edges=[world_contains_x['forward']], loss=BCEWithLogitsLoss())
+    # x[y0] = ModuleLearner(world_contains_x('x'), module=Net(), loss=BCEWithLogitsLoss())
+    x[y0] = ModuleLearner(world_contains_x('x'), module=Net())
+    x[y1] = ModuleLearner(world_contains_x('x'), module=Net(), loss=BCEWithLogitsLoss())
 
     program = POILossProgram(graph)
     return program
@@ -39,9 +41,9 @@ def main():
 
     program = model_declaration()
     data = [{
-        'x': [1.],
-        'y0': [1.,0.],
-        'y1': [0.,1.]
+        'x': [[1.]],
+        'y0': [[1.,0.]],
+        'y1': [[0.,1.]]
         }]
     program.train(data, train_epoch_num=10, Optim=lambda param: torch.optim.SGD(param, lr=1))
     print('Train loss:', program.model.loss)
diff --git a/examples/simple-logic/main_no_world.py b/examples/simple-logic/main_no_world.py
index b1d019b8b..f05374a70 100644
--- a/examples/simple-logic/main_no_world.py
+++ b/examples/simple-logic/main_no_world.py
@@ -1,7 +1,7 @@
 def model_declaration():
     import torch
     from regr.program import LearningBasedProgram
-    from regr.sensor.pytorch.sensors import ReaderSensor, TorchEdgeReaderSensor
+    from regr.sensor.pytorch.sensors import ReaderSensor
     from regr.sensor.pytorch.learners import ModuleLearner
     from regr.graph import Property
 
@@ -34,9 +34,9 @@ def main():
 
     program = model_declaration()
     data = [{
-        'x': [1.],
-        'y0': [1.,0.],
-        'y1': [0.,1.]
+        'x': [[1.]],
+        'y0': [[1.,0.]],
+        'y1': [[0.,1.]]
         }]
     program.train(data, train_epoch_num=10, Optim=lambda param: torch.optim.SGD(param, lr=1))
     print('Train loss:', program.model.loss)
diff --git a/examples/simple-logic/model.py b/examples/simple-logic/model.py
index bade2377a..30353cf44 100644
--- a/examples/simple-logic/model.py
+++ b/examples/simple-logic/model.py
@@ -27,8 +27,9 @@ class Net(torch.nn.Module):
     def __init__(self, w=None):
         super().__init__()
         if w is not None:
-            self.w = torch.nn.Parameter(torch.tensor(w).float().view(2, 1))
+            self.w = torch.nn.Parameter(torch.tensor(w).float().view(1, 2))
         else:
-            self.w = torch.nn.Parameter(torch.randn(2, 1))
+            self.w = torch.nn.Parameter(torch.randn(1, 2))
+
     def forward(self, x):
-        return torch.matmul(self.w, x)
+        return x.matmul(self.w)
diff --git a/regr/graph/dataNode.py b/regr/graph/dataNode.py
index 177116d7b..94c47662c 100644
--- a/regr/graph/dataNode.py
+++ b/regr/graph/dataNode.py
@@ -1,5 +1,6 @@
 import numpy as np
 import torch.cuda
+import torch
 from collections import OrderedDict, namedtuple
 import time
 from itertools import product
@@ -1003,8 +1004,50 @@ def __prepareILPData(self, *_conceptsRelations, dnFun = None, fun=None, epsilon
                 
         myilpOntSolver = ilpOntSolverFactory.getOntSolverInstance(myOntologyGraphs)
                         
-        return myilpOntSolver, infer_candidatesID, graphResultsForPhraseToken, graphResultsForPhraseRelation, graphResultsForTripleRelations, hardConstrains, candidates_currentConceptOrRelation
-    
+        return myilpOntSolver, infer_candidatesID, graphResultsForPhraseToken, graphResultsForPhraseRelation, graphResultsForTripleRelations, hardConstrains, candidates_currentConceptOrRelation)
+
+      def infer(self):
+        conceptsRelations = self.__collectConceptsAndRelations(self) 
+        
+        for c in conceptsRelations:
+            cRoot = self.__findRootConceptOrRelation(c)
+            dns = self.findDatanodes(select = cRoot)
+            
+            if not dns:
+                continue
+            
+            vs = []
+            
+            for dn in dns:
+                v = dn.getAttribute(c)
+                
+                if v is not None:
+                    vs.append(v[1])
+                else:
+                    vs.append(None)         
+            
+            t = torch.FloatTensor(vs)
+            t[torch.isnan(t)] = 0 # NAN  -> 0
+            
+            vM = torch.argmax(t).item() # argmax
+            
+            # Elements for softmax
+            tExp = torch.exp(t)
+            tExpSum = torch.sum(tExp).item()
+            
+            keyArgmax = "<" + c + ">/argmax"
+            keySoftMax = "<" + c + ">/softmax"
+            
+            # Add argmax and softmax to DataNodes
+            for dn in dns:
+                if dn.getInstanceID() == vM:
+                    dn.attributes[keyArgmax] = 1
+                else:
+                    dn.attributes[keyArgmax] = 0
+                
+                dnSoftmax = tExp[dn.getInstanceID()]/tExpSum
+                dn.attributes[keySoftMax] = dnSoftmax.item()
+
     def __getILPsolver(self, conceptsRelations = []):
         
         _conceptsRelations = []
@@ -1281,11 +1324,11 @@ def __updateConceptInfo(self,  usedGraph, conceptInfo, sensor):
                 conceptInfo['relationAttrsGraph'] = conceptInfo['relationAttrs']
                 
             conceptInfo['relationAttrs'] = {}
-            
-            conceptInfo['relationMode'] = sensor.mode
-            conceptInfo['relationAttrs']["src"] = sensor.src 
-            conceptInfo['relationAttrs']["dst"] = sensor.dst
-            
+          
+            conceptInfo['relationMode'] = sensor.relation.mode
+            conceptInfo['relationAttrs']["src"] = self.__findConcept(sensor.src.name, usedGraph)  
+            conceptInfo['relationAttrs']["dst"] = self.__findConcept(sensor.dst.name, usedGraph)  
+
             if conceptInfo['relationAttrs']["dst"] == conceptInfo['concept']:
                 conceptInfo['relationAttrData'] = True
 
diff --git a/regr/graph/relation.py b/regr/graph/relation.py
index d2c7d5129..1bda6b4e6 100644
--- a/regr/graph/relation.py
+++ b/regr/graph/relation.py
@@ -13,19 +13,19 @@
 
 
 class Transformed():
-    def __init__(self, relationfunction, property, fn=None):
-        self.relationfunction = relationfunction
-        if isinstance(property, str):
-            property = self.relationfunction.src[property]
+    def __init__(self, relation, property, fn=None):
+        self.relation = relation
+        if isinstance(property, (str, Relation)):
+            property = self.relation.src[property]
         self.property = property
         self.fn = fn
 
     def __call__(self, data_item, device=-1):
         value = self.property(data_item)
         try:
-            mapping = self.relationfunction.dst[self.relationfunction](data_item)
+            mapping = self.relation.dst[self.relation](data_item)
         except KeyError:
-            mapping = self.relationfunction.src[self.relationfunction.T](data_item).T
+            mapping = self.relation.src[self.relation.reversed](data_item).T
         mapping = mapping.to(dtype=torch.float, device=device)
         value = value.to(dtype=torch.float, device=device)
         if self.fn is None:
@@ -37,65 +37,13 @@ def __call__(self, data_item, device=-1):
         return self.fn(mapping * value)
 
 
-class RelationFunction():
-    def __init__(self, relation):
-        self.relation = relation
-        self.mode = "forward"
-
-    @property
-    def src(self):
-        return self.relation.src
-
-    @property
-    def dst(self):
-        return self.relation.dst
-
-    @property
-    def T(self):
-        return self.relation.bacward
-    
-    @property
-    def name(self):
-        return f'{self.relation.name}.{self.mode}'
-
-    def __call__(self, *props, fn=None):
-        return Transformed(self, props[0], fn=fn)
-        # TODO: support mapping multiple props together?
-        # for prop in props:
-        #     assert prop.sup == self.src
-        #     yield Transformed(self.relation, prop, fn=fn)
-
-    def __str__(self):
-        return f'{str(self.relation)}.{self.mode}'
-
-    def __repr__(self):
-        return f'{repr(self.relation)}.{self.mode}'
-
-
-class RelationBackwardFunction(RelationFunction):
-    def __init__(self, relation):
-        super().__init__(relation)
-        self.mode = "backward"
-
-    @property
-    def src(self):
-        return self.relation.dst
-
-    @property
-    def dst(self):
-        return self.relation.src
-
-    @property
-    def T(self):
-        return self.relation.forward
-
 @BaseGraphTree.localize_namespace
 class Relation(BaseGraphTree):
     @classmethod
     def name(cls):  # complicated to use class property, just function
         return cls.__name__
 
-    def __init__(self, src, dst, argument_name):
+    def __init__(self, src, dst, argument_name, reverse_of=None):
         cls = type(self)
         if isinstance(argument_name, str):
             name = argument_name
@@ -104,10 +52,25 @@ def __init__(self, src, dst, argument_name):
         BaseGraphTree.__init__(self, name)
         self.src = src
         self.dst = dst
-        src._out.setdefault(cls.name(), []).append(self)
-        dst._in.setdefault(cls.name(), []).append(self)
-        self.forward = RelationFunction(self)
-        self.backward = RelationBackwardFunction(self)
+        if reverse_of is None:
+            self.is_reversed = False
+            src._out.setdefault(cls.name(), []).append(self)
+            dst._in.setdefault(cls.name(), []).append(self)
+            reverse_of = Relation(dst, src, f'{name}.reversed', self)
+        else:
+            self.is_reversed = True
+        self.reversed = reverse_of
+
+    @property
+    def mode(self):
+        return 'backward' if self.is_reversed else 'forward'
+
+    def __call__(self, *props, fn=None):
+        return Transformed(self, props[0], fn=fn)
+        # TODO: support mapping multiple props together?
+        # for prop in props:
+        #     assert prop.sup == self.src
+        #     yield Transformed(self.relation, prop, fn=fn)
 
     @property
     def src(self):
diff --git a/regr/program/model/primaldual.py b/regr/program/model/primaldual.py
new file mode 100644
index 000000000..1cd79ad85
--- /dev/null
+++ b/regr/program/model/primaldual.py
@@ -0,0 +1,45 @@
+import logging
+
+import torch
+from torch.utils import data
+
+from ..model.pytorch import PoiModel
+
+
+class PrimalDualModel(PoiModel):
+    logger = logging.getLogger(__name__)
+
+    def __init__(self, graph, *args, **kwargs):
+        super().__init__(graph, *args, **kwargs)
+        nconstr = len(graph.logicalConstrains)
+        self.lmbd = torch.nn.Parameter(torch.empty(nconstr))
+        self.lmbd_p = torch.empty(nconstr) # none parameter
+        self.lmbd_index = {}
+        for i, (key, lc) in enumerate(graph.logicalConstrains.items()):
+            self.lmbd_index[key] = i
+            self.lmbd_p[i] = float(lc.p) / 100.
+        self.reset_parameters()
+
+    def reset_parameters(self):
+        torch.nn.init.constant_(self.lmbd, 0.5)
+
+    def get_lmbd(self, key):
+        return self.lmbd[self.lmbd_index[key]].clamp(max=self.lmbd_p[self.lmbd_index[key]])
+
+    def pd_loss(self, datanode):
+        # call the loss calculation
+        # returns a dictionary, keys are matching the constraints
+        constr_loss = datanode.calculateLcLoss()
+        if not constr_loss:
+            return 0
+        lmbd_loss = [self.get_lmbd(key) * loss['lossTensor'].clamp(min=0).sum() for key, loss in constr_loss.items()]
+        # lmbd_loss = torch.cat(lmbd_loss, dim=1).sum(dim=1)
+        # NB: there is no batch-dim in this loss
+        lmbd_loss = torch.stack(lmbd_loss).sum()
+        return lmbd_loss
+
+    def populate(self, builder):
+        loss, *outputs = super().populate(builder)
+        datanode = builder.getDataNode()
+        loss += self.pd_loss(datanode)
+        return (loss, *outputs)
diff --git a/regr/program/model/pytorch.py b/regr/program/model/pytorch.py
index e96d1d279..c9cd14621 100644
--- a/regr/program/model/pytorch.py
+++ b/regr/program/model/pytorch.py
@@ -5,7 +5,7 @@
 import torch.nn.functional as F
 
 from regr.graph import Property, Concept, DataNodeBuilder
-from regr.sensor.pytorch.sensors import TorchSensor, ReaderSensor, CacheSensor, TorchEdgeReaderSensor
+from regr.sensor.pytorch.sensors import TorchSensor, ReaderSensor, CacheSensor
 from regr.sensor.pytorch.learners import TorchLearner
 
 from .base import Mode
@@ -18,6 +18,7 @@ def __init__(self, graph):
         super().__init__()
         self.graph = graph
         self.mode(Mode.TRAIN)
+        self.build = True
 
         for learner in self.graph.get_sensors(TorchLearner):
             self.add_module(learner.fullname, learner.model)
@@ -63,7 +64,9 @@ def data_hash(self, data_item):
             ke_args = ke.args
             raise ValueError('To enable cache, data item must either contain a identifier key "id" or implement "__hass__" interface: \n{}'.format("\n".join(ke_args + te_args)))
 
-    def forward(self, data_item):
+    def forward(self, data_item, build=None):
+        if build is None:
+            build = self.build
         data_hash = None
         data_item = self.move(data_item)
 
@@ -72,11 +75,15 @@ def forward(self, data_item):
             sensor.fill_hash(data_hash)
         for sensor in self.graph.get_sensors(ReaderSensor):
             sensor.fill_data(data_item)
-        data_item.update({"graph": self.graph, 'READER': 0})
-        builder = DataNodeBuilder(data_item)
-        *out, = self.populate(builder)
-        datanode = builder.getDataNode()
-        return (*out, datanode)
+        if build:
+            data_item.update({"graph": self.graph, 'READER': 0})
+            builder = DataNodeBuilder(data_item)
+            *out, = self.populate(builder)
+            datanode = builder.getDataNode()
+            return (*out, datanode)
+        else:
+            return self.populate(data_item)
+
 
     def populate(self):
         raise NotImplementedError
@@ -183,7 +190,7 @@ def inference(self, builder):
         # data_item = self.solver.inferSelection(builder, list(self.poi))
         datanode = builder.getDataNode()
         # trigger inference
-        datanode.inferILPConstrains(*self.inference_with, fun=lambda val: torch.tensor(val).softmax(dim=-1).detach().cpu().numpy().tolist(), epsilon=None)
+        datanode.inferILPConstrains(*self.inference_with, fun=lambda val: torch.tensor(val, dtype=float).softmax(dim=-1).detach().cpu().numpy().tolist(), epsilon=None)
         return builder
 
     def populate(self, builder):
diff --git a/regr/program/primaldualprogram.py b/regr/program/primaldualprogram.py
new file mode 100644
index 000000000..0b32249df
--- /dev/null
+++ b/regr/program/primaldualprogram.py
@@ -0,0 +1,89 @@
+import logging
+import torch
+
+from .program import LearningBasedProgram
+from .model.primaldual import PrimalDualModel
+
+
+# Primal-dual need multiple backward through constraint loss.
+# It requires retain_graph=True.
+# Memory leak problem with `backward(create_graph=True)`
+# https://github.com/pytorch/pytorch/issues/4661#issuecomment-596526199
+# workaround with following functions based on `grad()`
+def backward(loss, parameters):
+    parameters = list(parameters)
+    grads = torch.autograd.grad(outputs=loss, inputs=parameters, retain_graph=False)
+    assert len(grads) == len(parameters)
+    for grad, parameter in zip(grads, parameters):
+        parameter.grad = grad
+
+def unset_backward(parameters):
+    for parameter in parameters:
+        parameter.grad = None
+
+def reverse_sign_grad(parameters, factor=-1.):
+    for parameter in parameters:
+        if parameter.grad is not None:
+            parameter.grad = factor * parameter.grad
+
+class PrimalDualProgram(LearningBasedProgram):
+    logger = logging.getLogger(__name__)
+
+    def __init__(self, graph, Model, CModel=None, **kwargs):
+        super().__init__(graph, Model, **kwargs)
+        if CModel is None:
+            CModel = PrimalDualModel
+        self.cmodel = CModel(graph)
+        self.copt = None
+
+    def train(
+        self,
+        training_set,
+        valid_set=None,
+        test_set=None,
+        device=None,
+        train_epoch_num=1,
+        Optim=None,
+        COptim=None):
+        if device is not None:
+            self.cmodel.to(device)
+        if COptim is not None and list(self.cmodel.parameters()):
+            self.copt = COptim(self.cmodel.parameters())
+        else:
+            self.copt = None
+        return super().train(
+            training_set,
+            valid_set=valid_set,
+            test_set=test_set,
+            device=device,
+            train_epoch_num=train_epoch_num,
+            Optim=Optim)
+
+    def train_epoch(self, dataset):
+        self.model.train()
+        self.cmodel.train()
+        for data in dataset:
+            mloss, metric, output = self.model(data)
+            closs = self.cmodel(output)
+            loss = mloss + closs
+            if self.opt is not None:
+                self.opt.zero_grad()
+                loss.backward()
+                #backward(loss, self.model.parameters())
+                self.opt.step()
+                # unset_backward(self.model.parameters())
+            # closs = self.cmodel(output)
+            # copt_loss = -closs
+            if self.copt is not None:
+                # self.copt.zero_grad()
+                # copt_loss.backward()
+                # NOTE: Based on the face the gradient of lambda in dual
+                #       is reversing signs of their gradient in primal,
+                #       we avoid a repeated backward (and also pytorch's
+                #       retain_graph problem), we simply reverse the sign
+                #       for the dual. Don't zero_grad() here!
+                reverse_sign_grad(self.cmodel.parameters())
+                # backward(copt_loss, self.cmodel.parameters())
+                self.copt.step()
+                # unset_backward(self.cmodel.parameters())
+            yield loss, metric, output
diff --git a/regr/sensor/pytorch/learners.py b/regr/sensor/pytorch/learners.py
index e79cd2dfa..b378391df 100644
--- a/regr/sensor/pytorch/learners.py
+++ b/regr/sensor/pytorch/learners.py
@@ -6,11 +6,11 @@
 import torch
 
 from .. import Learner
-from .sensors import TorchSensor, ModuleSensor
+from .sensors import TorchSensor, FunctionalSensor, ModuleSensor
 from .learnerModels import PyTorchFC, LSTMModel, PyTorchFCRelu
 
 
-class TorchLearner(Learner, TorchSensor):
+class TorchLearner(Learner, FunctionalSensor):
     __metaclass__ = abc.ABCMeta
 
     def __init__(self, *pre, edges=None, loss=None, metric=None, label=False, device='auto'):
diff --git a/regr/sensor/pytorch/query_sensor.py b/regr/sensor/pytorch/query_sensor.py
index f0d8e0cfd..d58f9e68b 100644
--- a/regr/sensor/pytorch/query_sensor.py
+++ b/regr/sensor/pytorch/query_sensor.py
@@ -1,16 +1,22 @@
 from typing import Dict, Any
+import torch
 
-from .sensors import TorchSensor, FunctionalSensor
+from .sensors import FunctionalSensor, FunctionalReaderSensor
 
 
 class QuerySensor(FunctionalSensor):
+    def __init__(self, *pres, **kwargs):
+        super().__init__(*pres, **kwargs)
+        self.kwinputs = {}
+
     @property
     def builder(self):
         builder = self.context_helper
         from ...graph import DataNodeBuilder
 
         if not isinstance(builder, DataNodeBuilder):
-            raise TypeError('{} should work with DataNodeBuilder.'.format(type(self)))
+            raise TypeError(f'{type(self)} should work with DataNodeBuilder.'
+                            'For example, set `build` option to `True` when running the program')
         return builder
 
     @property
@@ -23,20 +29,23 @@ def concept(self):
 
     def define_inputs(self):
         super().define_inputs()
-        if self.inputs is None:
-            self.inputs = []
-
         datanodes = self.builder.findDataNodesInBuilder(select=self.concept)
-        self.inputs.insert(0, datanodes)
+        self.kwinputs['datanodes'] = datanodes
+
+    def forward_wrap(self):
+        value = self.forward(*self.inputs, **self.kwinputs)
+        if isinstance(value, torch.Tensor) and value.device is not self.device:
+            value = value.to(device=self.device)
+        return value
 
 
 class DataNodeSensor(QuerySensor):
     def forward_wrap(self):
         from ...graph import Property
-        datanodes = self.inputs[0]
-        assert len(self.inputs[1:]) == len(self.pres)
+        datanodes = self.kwinputs['datanodes']
+        assert len(self.inputs) == len(self.pres)
         inputs = []
-        for input, pre in zip(self.inputs[1:], self.pres):
+        for input, pre in zip(self.inputs, self.pres):
             if isinstance(pre, str):
                 try:
                     pre = self.concept[pre]
@@ -49,21 +58,13 @@ def forward_wrap(self):
                 # otherwise, repeat the input
                 inputs.append([input] * len(datanodes))
 
-        return [self.forward(datanode, *input) for datanode, *input in zip(datanodes, *inputs)]
+        value = [self.forward(*input, datanode=datanode) for datanode, *input in zip(datanodes, *inputs)]
 
-
-class InstantiateSensor(TorchSensor):
-    def __call__(
-        self,
-        data_item: Dict[str, Any]
-    ) -> Dict[str, Any]:
-        try:
-            self.update_pre_context(data_item)
-        except:
-            print('Error during updating pre with sensor {}'.format(self))
-            raise
         try:
-            return data_item[self]
-        except KeyError:
-            return data_item[self.sup.sup['index']]
+            return torch.tensor(value, device=self.device)
+        except (TypeError, RuntimeError, ValueError):
+            return value
+
 
+class DataNodeReaderSensor(DataNodeSensor, FunctionalReaderSensor):
+    pass
diff --git a/regr/sensor/pytorch/relation_sensors.py b/regr/sensor/pytorch/relation_sensors.py
index c5a7519e7..7d5153f03 100644
--- a/regr/sensor/pytorch/relation_sensors.py
+++ b/regr/sensor/pytorch/relation_sensors.py
@@ -1,19 +1,16 @@
-from regr.sensor.pytorch.sensors import TorchSensor, FunctionalSensor, TriggerPrefilledSensor, JointSensor, FunctionalReaderSensor, ReaderSensor
+from regr.sensor.pytorch.sensors import FunctionalSensor, JointSensor, FunctionalReaderSensor, ReaderSensor
 from regr.sensor.sensor import Sensor
 from regr.graph.graph import Property
 from regr.sensor.pytorch.query_sensor import QuerySensor
-from typing import Any, Dict
+from typing import Any, Dict, OrderedDict
 import torch
 from itertools import product
 
 
 class EdgeSensor(FunctionalSensor):
-    modes = ("forward", "backward")
-
-    def __init__(self, *pres, relation, mode="forward", **kwargs):
+    def __init__(self, *pres, relation, **kwargs):
         super().__init__(*pres, **kwargs)
         self.relation = relation
-        self.mode = mode
 
     @property
     def relation(self):
@@ -22,27 +19,8 @@ def relation(self):
     @relation.setter
     def relation(self, relation):
         self._relation = relation
-        # try to update
-        try:
-            self.mode = self.mode
-        except:
-            pass
-
-    @property
-    def mode(self):
-        return self._mode
-
-    @mode.setter
-    def mode(self, mode):
-        if mode not in self.modes:
-            raise ValueError('The mode passed to the edge sensor must be one of %s' % self.modes)
-        self._mode = mode
-        if self.mode == "forward":
-            self.src = self.relation.src
-            self.dst = self.relation.dst
-        elif self.mode == "backward":
-            self.src = self.relation.dst
-            self.dst = self.relation.src
+        self.src = self.relation.src
+        self.dst = self.relation.dst
 
     def update_pre_context(
         self,
@@ -57,75 +35,70 @@ def fetch_value(self, pre, selector=None, concept=None):
         return super().fetch_value(pre, selector, concept)
 
 
-class CandidateSensor(EdgeSensor, QuerySensor):
+class BaseCandidateSensor(QuerySensor):
     @property
     def args(self):
-        return [self.dst, self.src]
+        raise NotImplementedError
 
     def define_inputs(self):
         super(QuerySensor, self).define_inputs()  # skip QuerySensor.define_inputs
-        if self.inputs is None:
-            self.inputs = []
-        args = []
-        for concept in self.args:
+        args = {}
+        for name, concept in self.args.items():
             datanodes = self.builder.findDataNodesInBuilder(select=concept)
-            args.append(datanodes)
-        self.inputs = args + self.inputs
+            args[name] = datanodes
+        self.kwinputs['datanodes'] = args
+
+class CandidateSensor(EdgeSensor, BaseCandidateSensor):
+    @property
+    def args(self):
+        return OrderedDict((('dst', self.dst), ('src', self.src)))
 
     def forward_wrap(self):
         # args
-        args = self.inputs[:len(self.args)]
+        args = self.kwinputs['datanodes']
         # functional inputs
-        inputs = self.inputs[len(self.args):]
+        inputs = self.inputs
 
         arg_lists = []
         dims = []
-        for arg_list in args:
+        for arg_list in args.values():
             arg_lists.append(enumerate(arg_list))
             dims.append(len(arg_list))
         output = torch.zeros(dims, dtype=torch.long, device=self.device)
         for arg_enum in product(*arg_lists):
             index, arg_list = zip(*arg_enum)
-            output[(*index,)] = self.forward(*arg_list, *inputs)
+            candidates = dict(zip(self.args.keys(), arg_list))
+            output[(*index,)] = self.forward(*inputs, **candidates)
         return output
 
 
-class CompositionCandidateSensor(JointSensor, QuerySensor):
+class CompositionCandidateSensor(JointSensor, BaseCandidateSensor):
     @property
     def args(self):
-        return [relation.src for relation in self.relations]
+        return OrderedDict((relation.reversed.name, relation.src) for relation in self.relations)
 
     def __init__(self, *args, relations, **kwargs):
         super().__init__(*args, **kwargs)
         self.relations = relations
 
-    def define_inputs(self):
-        super(QuerySensor, self).define_inputs()  # skip QuerySensor.define_inputs
-        if self.inputs is None:
-            self.inputs = []
-        args = []
-        for concept in self.args:
-            datanodes = self.builder.findDataNodesInBuilder(select=concept)
-            args.append(datanodes)
-        self.inputs = args + self.inputs
-
     def forward_wrap(self):
         # args
-        args = self.inputs[:len(self.args)]
+        args = self.kwinputs['datanodes']
         # functional inputs
-        inputs = self.inputs[len(self.args):]
+        inputs = self.inputs
 
         arg_lists = []
         indexes = []
         dims = []
-        for arg_list in args:
+        for arg_list in args.values():
             arg_lists.append(enumerate(arg_list))
             dims.append(len(arg_list))
             indexes.append([])
 
         for arg_enum in product(*arg_lists):
             index, arg_list = zip(*arg_enum)
-            if self.forward(*arg_list, *inputs):
+            candidates = dict(zip(self.args.keys(), arg_list))
+            if self.forward(*inputs, **candidates):
                 for i, index_ in enumerate(index):
                     indexes[i].append(index_)
 
@@ -143,8 +116,9 @@ def forward_wrap(self):
 class CandidateRelationSensor(CandidateSensor):
     @property
     def args(self):
+        # guess which side of the relation?
         concept = self.concept
-        return [(rel.dst if concept is rel.src else rel.src) for rel in self.relations]
+        return OrderedDict((rel.name, rel.dst) if concept is rel.src else (rel.reversed.name, rel.src) for rel in self.relations)
 
     def __init__(self, *pres, relations, edges=None, forward=None, label=False, device='auto'):
         super().__init__(*pres, edges=edges, forward=forward, label=label, device=device)
diff --git a/regr/sensor/pytorch/sensors.py b/regr/sensor/pytorch/sensors.py
index d658a33ca..622a4116b 100644
--- a/regr/sensor/pytorch/sensors.py
+++ b/regr/sensor/pytorch/sensors.py
@@ -122,13 +122,13 @@ def update_pre_context(
         data_item: Dict[str, Any],
         concept=None
     ):
-        from ...graph.relation import Transformed
+        from ...graph.relation import Transformed, Relation
         concept = concept or self.concept
         for edge in self.edges:
             for sensor in edge.find(self.non_label_sensor):
                 sensor(data_item)
         for pre in self.pres:
-            if isinstance(pre, str):
+            if isinstance(pre, (str, Relation)):
                 try:
                     pre = concept[pre]
                 except KeyError:
@@ -139,7 +139,7 @@ def update_pre_context(
                 for sensor in pre.find(self.non_label_sensor):
                     sensor(data_item)
             elif isinstance(pre, Transformed):
-                self.concept[pre.relationfunction](data_item)
+                self.concept[pre.relation](data_item)
                 for sensor in pre.property.find(self.non_label_sensor):
                     sensor(data_item)
 
@@ -226,7 +226,7 @@ def components(self):
         return self._components
 
     def attached(self, sup):
-        from ...graph.relation import RelationFunction
+        from ...graph.relation import Relation
         from .relation_sensors import EdgeSensor
         super().attached(sup)
         if isinstance(self.prop.name, tuple):
@@ -234,8 +234,8 @@ def attached(self, sup):
             self._components = []
             for name in self.prop.name:
                 index = len(self.components)
-                if isinstance(name, RelationFunction):
-                    sensor = EdgeSensor(self, forward=lambda x, index=index: x[index], relation=name.relation, mode=name.mode)
+                if isinstance(name, Relation):
+                    sensor = EdgeSensor(self, forward=lambda x, index=index: x[index], relation=name)
                 else:
                     sensor = FunctionalSensor(self, forward=lambda x, index=index: x[index])
                 self.concept[name] = sensor
@@ -342,7 +342,7 @@ def cache(SensorClass, CacheSensorClass=CacheSensor):
 
 
 class ReaderSensor(ConstantSensor):
-    def __init__(self, *args, keyword=None, **kwargs):
+    def __init__(self, *args, keyword, **kwargs):
         super().__init__(*args, data=None, **kwargs)
         self.keyword = keyword
 
@@ -485,41 +485,41 @@ def concept(self):
         raise TypeError('{} is not to be associated with a concept.'.format(type(self)))
 
 
-class TorchEdgeReaderSensor(TorchEdgeSensor, ReaderSensor):
-    def __init__(self, *pres, to, keyword, mode="forward", edges=None, label=False, device='auto'):
-        super().__init__(*pres, to=to, mode=mode, edges=edges, label=label, device=device)
-        self.keyword = keyword
-        self.data = None
+# class TorchEdgeReaderSensor(TorchEdgeSensor, ReaderSensor):
+#     def __init__(self, *pres, to, keyword, mode="forward", edges=None, label=False, device='auto'):
+#         super().__init__(*pres, to=to, mode=mode, edges=edges, label=label, device=device)
+#         self.keyword = keyword
+#         self.data = None
 
 
-class ModuleEdgeSensor(TorchEdgeSensor):
-    def __init__(self, *pres, to, module, mode="forward", edges=None, label=False, device='auto'):
-        self.module = module
-        super().__init__(self, *pres, to=to, mode=mode, edges=edges, label=label, device=device)
+# class ModuleEdgeSensor(TorchEdgeSensor):
+#     def __init__(self, *pres, to, module, mode="forward", edges=None, label=False, device='auto'):
+#         self.module = module
+#         super().__init__(self, *pres, to=to, mode=mode, edges=edges, label=label, device=device)
 
-    def forward(self, *inputs):
-        return self.module(*inputs)
+#     def forward(self, *inputs):
+#         return self.module(*inputs)
 
 
-class ForwardEdgeSensor(TorchEdgeSensor):
-    def forward(self, input) -> Any:
-        return input
+# class ForwardEdgeSensor(TorchEdgeSensor):
+#     def forward(self, input) -> Any:
+#         return input
 
     
-class ConstantEdgeSensor(TorchEdgeSensor):
-    def __init__(self, *pres, to, data, mode="forward", edges=None, label=False, as_tensor=True, device='auto'):
-        super().__init__(*pres, to=to, mode=mode, edges=edges, label=label, device=device)
-        self.data = data
-        self.as_tensor = as_tensor
-
-    def forward(self, *_) -> Any:
-        try:
-            if self.as_tensor:
-                return torch.tensor(self.data, device=self.device)
-            else:
-                self.data
-        except (TypeError, RuntimeError, ValueError):
-            return self.data
+# class ConstantEdgeSensor(TorchEdgeSensor):
+#     def __init__(self, *pres, to, data, mode="forward", edges=None, label=False, as_tensor=True, device='auto'):
+#         super().__init__(*pres, to=to, mode=mode, edges=edges, label=label, device=device)
+#         self.data = data
+#         self.as_tensor = as_tensor
+
+#     def forward(self, *_) -> Any:
+#         try:
+#             if self.as_tensor:
+#                 return torch.tensor(self.data, device=self.device)
+#             else:
+#                 self.data
+#         except (TypeError, RuntimeError, ValueError):
+#             return self.data
 
         
 class AggregationSensor(TorchSensor):
@@ -621,61 +621,61 @@ def forward(self,) -> Any:
             return torch.zeros(1, 1, self.default_dim, device=self.device)
 
 
-class SelectionEdgeSensor(TorchEdgeSensor):
-    def __init__(self, *pres, mode="selection", device='auto'):
-        super().__init__(*pres, mode=mode, device=device)
-        self.selection_helper = None
-
-    def __call__(
-        self,
-        data_item: Dict[str, Any]
-    ) -> Dict[str, Any]:
-        self.get_initialized()
-        try:
-            self.update_pre_context(data_item)
-        except:
-            print('Error during updating pre data item with sensor {}'.format(self))
-            raise
-        self.context_helper = data_item
-        try:
-            self.update_context(data_item)
-        except:
-            print('Error during updating data item with sensor {}'.format(self))
-            raise
-        return data_item[self.src[self.dst]]
-
-    def get_selection_helper(self):
-        self.selection_helper = self.context_helper[self.src[self.dst]]
-
-    def update_context(
-        self,
-        data_item: Dict[str, Any],
-        force=False):
-        if not force and self in data_item:
-            # data_item cached results by sensor name. override if forced recalc is needed
-            val = data_item[self]
-        else:
-            self.define_inputs()
-            self.get_selection_helper()
-            val = self.forward()
-        if val is not None:
-            data_item[self] = val
-            data_item[self.prop] = val  # override state under property name
-
-
-class ProbabilitySelectionEdgeSensor(SelectionEdgeSensor):
-    def forward(self,) -> Any:
-        return self.selection_helper
-
-
-class ThresholdSelectionEdgeSensor(SelectionEdgeSensor):
-    def __init__(self, *pres, threshold=0.5, device='auto'):
-        # FIXME: @hfaghihi, do you mean to call super class of `SelectionEdgeSensor`, so here we skip the constructor of `SelectionEdgeSensor`?
-        super(SelectionEdgeSensor).__init__(*pres, device=device)
-        self.threshold = threshold
-
-    def forward(self,) -> Any:
-        return torch.tensor([x for x in self.selection_helper if x >= self.threshold], device=self.device)
+# class SelectionEdgeSensor(TorchEdgeSensor):
+#     def __init__(self, *pres, mode="selection", device='auto'):
+#         super().__init__(*pres, mode=mode, device=device)
+#         self.selection_helper = None
+
+#     def __call__(
+#         self,
+#         data_item: Dict[str, Any]
+#     ) -> Dict[str, Any]:
+#         self.get_initialized()
+#         try:
+#             self.update_pre_context(data_item)
+#         except:
+#             print('Error during updating pre data item with sensor {}'.format(self))
+#             raise
+#         self.context_helper = data_item
+#         try:
+#             self.update_context(data_item)
+#         except:
+#             print('Error during updating data item with sensor {}'.format(self))
+#             raise
+#         return data_item[self.src[self.dst]]
+
+#     def get_selection_helper(self):
+#         self.selection_helper = self.context_helper[self.src[self.dst]]
+
+#     def update_context(
+#         self,
+#         data_item: Dict[str, Any],
+#         force=False):
+#         if not force and self in data_item:
+#             # data_item cached results by sensor name. override if forced recalc is needed
+#             val = data_item[self]
+#         else:
+#             self.define_inputs()
+#             self.get_selection_helper()
+#             val = self.forward()
+#         if val is not None:
+#             data_item[self] = val
+#             data_item[self.prop] = val  # override state under property name
+
+
+# class ProbabilitySelectionEdgeSensor(SelectionEdgeSensor):
+#     def forward(self,) -> Any:
+#         return self.selection_helper
+
+
+# class ThresholdSelectionEdgeSensor(SelectionEdgeSensor):
+#     def __init__(self, *pres, threshold=0.5, device='auto'):
+#         # FIXME: @hfaghihi, do you mean to call super class of `SelectionEdgeSensor`, so here we skip the constructor of `SelectionEdgeSensor`?
+#         super(SelectionEdgeSensor).__init__(*pres, device=device)
+#         self.threshold = threshold
+
+#     def forward(self,) -> Any:
+#         return torch.tensor([x for x in self.selection_helper if x >= self.threshold], device=self.device)
 
 
 class ConcatSensor(TorchSensor):
diff --git a/regr/sensor/pytorch/tokenizers/spacy.py b/regr/sensor/pytorch/tokenizers/spacy.py
index e5ffc1248..9b684fab4 100644
--- a/regr/sensor/pytorch/tokenizers/spacy.py
+++ b/regr/sensor/pytorch/tokenizers/spacy.py
@@ -4,8 +4,8 @@
 
 
 class SpacyTokenizer(EdgeSensor):
-    def __init__(self, *pres, relation, mode="forward", edges=None, label=False, device='auto', spacy=None):
-        super().__init__(*pres, relation=relation, mode=mode, edges=edges, label=label, device=device)
+    def __init__(self, *pres, relation, edges=None, label=False, device='auto', spacy=None):
+        super().__init__(*pres, relation=relation, edges=edges, label=label, device=device)
         if not spacy:
             raise ValueError('You should select a default Spacy Pipeline')
         self.nlp = spacy
diff --git a/regr/sensor/pytorch/tokenizers/transformers.py b/regr/sensor/pytorch/tokenizers/transformers.py
index 8323479ff..8b92af140 100644
--- a/regr/sensor/pytorch/tokenizers/transformers.py
+++ b/regr/sensor/pytorch/tokenizers/transformers.py
@@ -4,8 +4,8 @@
 
 
 class TokenizerEdgeSensor(EdgeSensor, JointSensor):
-    def __init__(self, *pres, relation, mode="forward", edges=None, label=False, device='auto', tokenizer=None):
-        super().__init__(*pres, relation=relation, mode=mode, edges=edges, label=label, device=device)
+    def __init__(self, *pres, relation, edges=None, label=False, device='auto', tokenizer=None):
+        super().__init__(*pres, relation=relation, edges=edges, label=label, device=device)
         if not tokenizer:
             raise ValueError('You should select a default Tokenizer')
         self.tokenizer = tokenizer


Test patch:
diff --git a/examples/CIFAR10/main_test.py b/examples/CIFAR10/main_test.py
new file mode 100644
index 000000000..ac4682583
--- /dev/null
+++ b/examples/CIFAR10/main_test.py
@@ -0,0 +1,231 @@
+import sys
+import torch
+from data.reader import EmailSpamReader
+from regr.program.model.pytorch import PoiModel, IMLModel
+# from regr.program.metric import MacroAverageTracker, ValueTracker
+from regr.program.metric import MacroAverageTracker, PRF1Tracker, ValueTracker
+
+from torchvision import datasets, transforms
+from PIL import Image
+import torch.nn.functional as F
+import torch.nn as nn
+import os,pickle
+import numpy as np
+from regr.program.loss import NBCrossEntropyLoss
+
+sys.path.append('.')
+sys.path.append('../..')
+
+def prediction_softmax(pr, gt):
+    return torch.softmax(pr.data, dim=-1)
+
+# image[‘emb] = ModuleLearner(‘pixels’)
+# image[Felan] = ModuleLearner(‘emb’, net)
+class ImageNetwork(torch.nn.Module):
+    def __init__(self, n_outputs=2):
+        super(ImageNetwork, self).__init__()
+        self.num_outputs = n_outputs
+        self.conv1 = nn.Conv2d(3, 6, 5)
+        self.pool = nn.MaxPool2d(2, 2)
+        self.conv2 = nn.Conv2d(6, 16, 5)
+
+
+        self.fc = nn.Linear(16 * 5 * 5, n_outputs)
+
+        # self.fc1 = nn.Linear(16 * 5 * 5, 120)
+        # self.fc2 = nn.Linear(120, 84)
+        # self.fc = nn.Linear(84, n_outputs)
+
+    def forward(self, x):
+        x = F.relu(self.conv1(x))
+        x = self.pool(x)
+        x = F.relu(self.conv2(x))
+        x = self.pool(x)
+        x = x.view(-1, 16 * 5 * 5)
+        # x = F.relu(self.fc1(x))
+        # x = F.relu(self.fc2(x))
+        x = self.fc(x)
+        return F.softmax(x, dim=-1)
+
+class ImageModel(PoiModel):
+    def __init__(self, graph):
+        super().__init__(
+            graph,
+            loss=MacroAverageTracker(NBCrossEntropyLoss()),
+            metric=PRF1Tracker())
+
+from graph import graph, airplane, dog, truck
+
+def model_declaration():
+    from regr.sensor.pytorch.sensors import ReaderSensor, ForwardEdgeSensor, ConstantSensor, ConcatSensor
+    from regr.sensor.pytorch.learners import ModuleLearner
+    # from regr.sensor.pytorch.relation_sensors import CandidateReaderSensor
+    from regr.program import LearningBasedProgram
+    from regr.program.model.pytorch import PoiModel
+    import torch
+    from torch import nn
+    graph.detach()
+    image = graph['image']
+    airplane = graph['airplane']
+    dog = graph['dog']
+    truck = graph['truck']
+
+    image['pixels'] = ReaderSensor(keyword='pixels')
+    image[airplane] = ReaderSensor(keyword='airplane',label=True)
+    image[dog] = ReaderSensor(keyword='dog',label=True)
+    image[truck] = ReaderSensor(keyword='truck',label=True)
+
+    image[airplane] = ModuleLearner('pixels', module=ImageNetwork())
+    image[dog] = ModuleLearner('pixels', module=ImageNetwork())
+    image[truck] = ModuleLearner('pixels', module=ImageNetwork())
+    program = LearningBasedProgram(graph, ImageModel)
+
+    return program
+
+class CIFAR10_subset(datasets.CIFAR10):
+
+    def __init__(self, root, train=True, transform=None, target_transform=None,
+                 download=False):
+
+        super(CIFAR10_subset, self).__init__(root, transform=transform,
+                                      target_transform=target_transform)
+
+        self.train = train  # training set or test set
+
+        if download:
+            self.download()
+
+        if not self._check_integrity():
+            raise RuntimeError('Dataset not found or corrupted.' +
+                               ' You can use download=True to download it')
+
+        if self.train:
+            downloaded_list = self.train_list
+        else:
+            downloaded_list = self.test_list
+
+        self.data = []
+        self.targets = []
+
+        # now load the picked numpy arrays
+        for file_name, checksum in downloaded_list:
+            file_path = os.path.join(self.root, self.base_folder, file_name)
+            with open(file_path, 'rb') as f:
+                if sys.version_info[0] == 2:
+                    entry = pickle.load(f)
+                else:
+                    entry = pickle.load(f, encoding='latin1')
+
+                a = np.array(entry['labels'])
+                selected_ix = []
+                selected_ix.extend(np.where(a == 0)[0][:100])# airplane
+                selected_ix.extend(np.where(a == 5)[0][:100])# dog
+                selected_ix.extend(np.where(a == 9)[0][:100])# truck
+                selected_data = [entry['data'][i] for i in range(len(entry['data'])) if i in selected_ix]
+                selected_labels = [entry['labels'][i] for i in range(len(entry['labels'])) if i in selected_ix]
+                self.data.append(selected_data)
+                # self.data.append(entry['data'])
+                if 'labels' in entry:
+                    self.targets.extend(selected_labels)
+                    # self.targets.extend(entry['labels'])
+                else:
+                    self.targets.extend(entry['fine_labels'])
+
+        self.data = np.vstack(self.data).reshape(-1, 3, 32, 32)
+        self.data = self.data.transpose((0, 2, 3, 1))  # convert to HWC
+
+        self._load_meta()
+
+    def __getitem__(self, index):
+        """
+        Args:
+            index (int): Index
+
+        Returns:
+            tuple: (image, target) where target is index of the target class.
+        """
+        img, target = self.data[index], self.targets[index]
+
+        # doing this so that it is consistent with all other datasets
+        # to return a PIL Image
+        img = Image.fromarray(img)
+
+        if self.transform is not None:
+            img = self.transform(img)
+        if self.target_transform is not None:
+            target = self.target_transform(target)
+
+        img = img.unsqueeze(0)
+
+        dict = {}
+        dict['pixels'] = img
+        if target == 0:
+            dict['airplane'] = [1]
+            dict['dog'] = [0]
+            dict['truck'] = [0]
+        elif target == 5:
+            dict['airplane'] = [0]
+            dict['dog'] = [1]
+            dict['truck'] = [0]
+        elif target == 9:
+            dict['airplane'] = [0]
+            dict['dog'] = [0]
+            dict['truck'] = [1]
+        else:
+            return None
+
+        return dict
+
+def load_cifar10(train=True, root='./data/', size=32):
+    CIFAR100_TRAIN_MEAN = (0.5071, 0.4867, 0.4408)
+    CIFAR100_TRAIN_STD = (0.2675, 0.2565, 0.2761)
+
+    if train:
+        transform = transforms.Compose(
+            [transforms.Resize(size),
+             transforms.RandomCrop(size, padding=round(size/8)),
+             transforms.RandomHorizontalFlip(),
+             transforms.ToTensor(),
+             transforms.Normalize(CIFAR100_TRAIN_MEAN, CIFAR100_TRAIN_STD)])
+    else:
+        transform = transforms.Compose(
+            [transforms.Resize(size),
+             transforms.ToTensor(),
+             transforms.Normalize(CIFAR100_TRAIN_MEAN, CIFAR100_TRAIN_STD)])
+
+    return CIFAR10_subset(root=root, train=train, transform=transform,download=False)
+
+def main():
+    program = model_declaration()
+
+    ### load data
+    trainset = load_cifar10(train=True)
+    # testset = load_cifar10(train=False)
+
+    program.train(trainset, train_epoch_num=2, Optim=lambda param: torch.optim.SGD(param, lr=.001))
+    program.test(trainset)
+    for datanode in program.populate(dataset=trainset):
+        print('airplane:', datanode.getAttribute(airplane))
+        print('dog:', datanode.getAttribute(dog))
+        print('truck:', datanode.getAttribute(truck))
+
+
+        # datanode.inferILPConstrains(fun=lambda val: torch.tensor(val).softmax(dim=-1).detach().cpu().numpy().tolist(),
+        #                             epsilon=None)
+        # print('inference airplane:', datanode.getAttribute(airplane, 'ILP'))
+        # print('inference dog:', datanode.getAttribute(dog, 'ILP'))
+        # print('inference truck:', datanode.getAttribute(truck, 'ILP'))
+
+
+    # for loss, metric, x_node in program.test(trainset):
+    #     print('loss:', loss)
+    #     print(metric)
+    #     # print('airplane:', torch.softmax(x_node.getAttribute('airplane'), dim=-1))
+    #     # print('dog:', torch.softmax(x_node.getAttribute('dog'), dim=-1))
+    #     # print('truck:', torch.softmax(x_node.getAttribute('truck'), dim=-1))
+    #
+    #     # print('y0:', x_node.getAttribute('<y0>/ILP'))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/SentimentAnalysis/datanode_torch_test.py b/examples/SentimentAnalysis/datanode_torch_test.py
index d819a8dda..dd2f00b05 100644
--- a/examples/SentimentAnalysis/datanode_torch_test.py
+++ b/examples/SentimentAnalysis/datanode_torch_test.py
@@ -44,8 +44,8 @@
     sensor(data_item)
     
 class ButDetector(DataNodeSensor):
-    def forward(self, instance, *inputs) -> Any:
-        if " but " in instance.getAttribute('index').lower():
+    def forward(self, *inputs, datanode) -> Any:
+        if " but " in datanode.getAttribute('index').lower():
             return True
         else:
             return False
@@ -56,8 +56,8 @@ def forward(self, ) -> Any:
 
 
 class SpacyVector(DataNodeSensor):
-    def forward(self, instance, *inputs):
-        return instance.getAttribute('index').vector
+    def forward(self, *inputs, datanode):
+        return datanode.getAttribute('index').vector
     
 class SentenceRepSensor(DataNodeSensor):
     def __init__(self, *pres, edges=None, forward= None ,label=False, device='auto', spacy=None):
@@ -66,8 +66,8 @@ def __init__(self, *pres, edges=None, forward= None ,label=False, device='auto',
             self.nlp = spacy
         else:
             raise ValueError("spacy should be instantiated")
-    def forward(self, instance, *inputs) -> Any:
-        text = self.nlp(instance.getAttribute(self.pres[0]))
+    def forward(self, *inputs, datanode) -> Any:
+        text = self.nlp(datanode.getAttribute(self.pres[0]))
         #return torch.from_numpy(text.vector).to(device=self.device)
         return text.vector
 
diff --git a/test_regr/examples/conll04/test_main.py b/test_regr/examples/conll04/test_main.py
index d0b0078ad..f59ded9d6 100644
--- a/test_regr/examples/conll04/test_main.py
+++ b/test_regr/examples/conll04/test_main.py
@@ -133,8 +133,8 @@ def model_declaration(config, case):
 
     sentence['raw'] = TestSensor(expected_outputs=case.sentence.raw)
 
-    # Edge: sentence to word forward
-    word[rel_sentence_contains_word.forward, 'raw'] = TestSensor(
+    # Edge: sentence to word
+    word[rel_sentence_contains_word, 'raw'] = TestSensor(
         sentence['raw'],
         expected_inputs=(case.sentence.raw,),
         expected_outputs=(case.word.scw, case.word.raw))
@@ -143,34 +143,33 @@ def model_declaration(config, case):
         expected_inputs=(case.word.raw,),
         expected_outputs=case.word.emb)
 
-    # Edge: word to char forward
-    char[rel_word_contains_char.forward, 'raw'] = TestSensor(
+    # Edge: word to char
+    char[rel_word_contains_char, 'raw'] = TestSensor(
         word['raw'],
         expected_inputs=(case.word.raw,),
         expected_outputs=(case.char.wcc, case.char.raw))
 
-    # Edge: word to phrase backward
-    phrase[rel_phrase_contains_word.backward, 'raw'] = TestSensor(
+    # Edge: word to phrase reversed
+    phrase[rel_phrase_contains_word.reversed, 'raw'] = TestSensor(
         word['emb'],
         expected_inputs=(case.word.emb,),
         expected_outputs=(case.phrase.pcw_backward, case.phrase.raw))
     phrase['emb'] = TestSensor(
-        rel_phrase_contains_word.backward('emb'),
+        rel_phrase_contains_word.reversed('emb'),
         expected_inputs=(case.phrase.emb,),
         expected_outputs=case.phrase.emb)
-    phrase[rel_sentence_contains_phrase.forward] = TestEdgeSensor(
-        rel_phrase_contains_word.backward(word[rel_sentence_contains_word.forward], fn=lambda x: x.max(1)[0]),
+    phrase[rel_sentence_contains_phrase] = TestEdgeSensor(
+        rel_phrase_contains_word.reversed(word[rel_sentence_contains_word], fn=lambda x: x.max(1)[0]),
         relation=rel_sentence_contains_phrase,
-        mode='forward',
         expected_inputs=(case.phrase.scp,),
         expected_outputs=case.phrase.scp)
 
-    pair[rel_pair_word1.backward, rel_pair_word2.backward] = TestSensor(
+    pair[rel_pair_word1.reversed, rel_pair_word2.reversed] = TestSensor(
         word['emb'],
         expected_inputs=(case.word.emb,),
         expected_outputs=(case.pair.pa1_backward, case.pair.pa2_backward))
     pair['emb'] = TestSensor(
-        rel_pair_word1.backward('emb'), rel_pair_word2.backward('emb'),
+        rel_pair_word1.reversed('emb'), rel_pair_word2.reversed('emb'),
         expected_inputs=(case.pair.emb[:,:2048],case.pair.emb[:,2048:]),
         expected_outputs=case.pair.emb)
 
@@ -312,6 +311,8 @@ def test_main_conll04(case):
 
     _, _, datanode = lbp.model(data)
 
+    datanode.infer()
+    
     for child_node in datanode.getChildDataNodes():
         if child_node.ontologyNode.name == 'word':
             assert child_node.getAttribute('raw') == case.word.raw[child_node.instanceID]
@@ -322,7 +323,6 @@ def test_main_conll04(case):
             #assert len(child_node.getChildDataNodes()) == len(case.char.raw[child_node.instanceID])
             num_pairs = (torch.max(case.pair.pa1_backward, case.pair.pa2_backward))[:,child_node.instanceID].sum()
             assert len(child_node.getRelationLinks(relationName = "pair")) == num_pairs # has relation named "pair"with each word (including itself)
-            
             assert (child_node.getAttribute('emb') == case.word.emb[child_node.instanceID]).all()
             assert (child_node.getAttribute('<people>') == case.word.people[child_node.instanceID]).all()
             assert (child_node.getAttribute('<organization>') == case.word.organization[child_node.instanceID]).all()
diff --git a/test_regr/examples/edges/main.py b/test_regr/examples/edges/main.py
index 8cf2dd29d..970863b71 100644
--- a/test_regr/examples/edges/main.py
+++ b/test_regr/examples/edges/main.py
@@ -15,7 +15,6 @@
 def test_edge_main():
     from regr.sensor.pytorch.relation_sensors import EdgeSensor
     from regr.sensor.pytorch.sensors import ConstantSensor, JointSensor
-    from regr.sensor.pytorch.relation_sensors import CandidateEqualSensor, CandidateRelationSensor
     from graph import sentence, word, sentence_con_word, word1, word_equal_word1, graph, pair_word1, pair_word2, pair
     from regr.sensor.sensor import Sensor
     from regr.graph import DataNodeBuilder
@@ -28,7 +27,7 @@ def forward(self, text) -> Any:
     sensor1 = ConstantSensor(data='This is a sample sentence to check the phrase equality or in this case the words.')
     sentence['index'] = sensor1
     sentence['joint1', 'joint2'] = JointSensor(forward=lambda : ((1,2,3), ('a', 'b', 'c')))
-    word['index', 'ids'] = SampleEdge('index', relation=sentence_con_word, mode="forward")
+    word['index', 'ids'] = SampleEdge('index', relation=sentence_con_word)
     word1['index'] = ConstantSensor(data=['words', 'case', 'quality', 'is'])
 
     data_item = DataNodeBuilder({"graph": graph})
diff --git a/test_regr/examples/edges/main_torch.py b/test_regr/examples/edges/main_torch.py
index 5f2d2599c..70fdd4f40 100644
--- a/test_regr/examples/edges/main_torch.py
+++ b/test_regr/examples/edges/main_torch.py
@@ -14,8 +14,7 @@
 
 def test_edge_main():
     from regr.sensor.pytorch.relation_sensors import EdgeSensor
-    from regr.sensor.pytorch.sensors import ConstantSensor, JointSensor
-    from regr.sensor.pytorch.relation_sensors import CandidateEqualSensor, CandidateRelationSensor
+    from regr.sensor.pytorch.sensors import ConstantSensor
     from graph import sentence, word, sentence_con_word, word1, word_equal_word1, graph, pair_word1, pair_word2, pair
     from regr.sensor.sensor import Sensor
     from regr.graph import DataNodeBuilder
@@ -23,8 +22,8 @@ def test_edge_main():
 
     import spacy
     class SpacyGloveRep(EdgeSensor):
-        def __init__(self, *pres, relation, mode="forward", edges=None, label=False, device='auto', spacy=None):
-            super().__init__(*pres, relation=relation, mode=mode, edges=edges, label=label, device=device)
+        def __init__(self, *pres, relation, edges=None, label=False, device='auto', spacy=None):
+            super().__init__(*pres, relation=relation, edges=edges, label=label, device=device)
             if not spacy:
                 raise ValueError('You should select a default Tokenizer')
             self.spacy = spacy
@@ -37,7 +36,7 @@ def forward(self, text) -> Any:
         
     sensor1 = ConstantSensor(data='This is a sample sentence to check the phrase equality or in this case the words.')
     sentence['index'] = sensor1
-    word['spacy'] = SpacyGloveRep('index', relation=sentence_con_word, mode='forward', spacy=spacy.load('en_core_web_lg'))
+    word['spacy'] = SpacyGloveRep('index', relation=sentence_con_word, spacy=spacy.load('en_core_web_lg'))
 
     data_item = DataNodeBuilder({"graph": graph})
     assert sensor1(data_item) == 'This is a sample sentence to check the phrase equality or in this case the words.'
diff --git a/test_regr/examples/equality/test_equality_main.py b/test_regr/examples/equality/test_equality_main.py
index 00271efaa..11687bed9 100644
--- a/test_regr/examples/equality/test_equality_main.py
+++ b/test_regr/examples/equality/test_equality_main.py
@@ -10,8 +10,7 @@
 
 
 def model_declaration():
-    from regr.sensor.pytorch.sensors import TorchSensor, ReaderSensor, TorchEdgeReaderSensor, ForwardEdgeSensor, \
-        ConstantSensor, ConstantEdgeSensor
+    from regr.sensor.pytorch.sensors import TorchSensor, ReaderSensor, ConstantSensor
     from regr.sensor.pytorch.relation_sensors import CandidateSensor
     from regr.program import LearningBasedProgram
     from regr.program.model.pytorch import model_helper, PoiModel
@@ -21,19 +20,21 @@ def model_declaration():
     graph.detach()
 
     sentence['text'] = ConstantSensor(data='This is a sample sentence to check the phrase equality or in this case the words.')
-    word[sentence_con_word.forward, 'text'] = Tokenizer(sentence['text'], relation=sentence_con_word, mode='forward', tokenizer=BertTokenizer.from_pretrained('bert-base-uncased'))
+    word[sentence_con_word, 'text'] = Tokenizer(sentence['text'], relation=sentence_con_word, tokenizer=BertTokenizer.from_pretrained('bert-base-uncased'))
     word1['label'] = ConstantSensor(data=[1, 1, 1])
     word1['span'] = ConstantSensor(data=[(0, 3), (7, 12), (20, 26)])
 
     word['span'] = TokenizerSpan('text', tokenizer=BertTokenizer.from_pretrained('bert-base-uncased'))
 
-    def makeSpanPairs(word, word1, *_):
+    def makeSpanPairs(*_, dst, src):
+        word = dst
+        word1 = src
         if word.getAttribute('span')[0] == word1.getAttribute('span')[0] and word.getAttribute('span')[1] == word1.getAttribute('span')[1]:
             return True
         else:
             return False
 
-    word[word_equal_word1.backward] = CandidateSensor(word['span'], word1['label'], word1['span'],  forward=makeSpanPairs, relation=word_equal_word1, mode='backward')
+    word[word_equal_word1.reversed] = CandidateSensor(word['span'], word1['label'], word1['span'],  forward=makeSpanPairs, relation=word_equal_word1.reversed)
         
     program = LearningBasedProgram(graph, model_helper(PoiModel, poi=[word]))
     return program
diff --git a/test_regr/examples/graph_coloring/main.py b/test_regr/examples/graph_coloring/main.py
index 69f08e1a4..37910cf3a 100644
--- a/test_regr/examples/graph_coloring/main.py
+++ b/test_regr/examples/graph_coloring/main.py
@@ -6,8 +6,10 @@
 
 
 def model_declaration():
-    from regr.sensor.pytorch.sensors import TorchSensor, ReaderSensor, TorchEdgeReaderSensor, ForwardEdgeSensor, ConstantSensor
-    from regr.sensor.pytorch.relation_sensors import CandidateReaderSensor
+    import torch
+
+    from regr.sensor.pytorch.sensors import ReaderSensor
+    from regr.sensor.pytorch.relation_sensors import EdgeSensor, CompositionCandidateReaderSensor
     from regr.program import LearningBasedProgram
     from regr.program.model.pytorch import model_helper, PoiModel
 
@@ -18,21 +20,23 @@ def model_declaration():
 
     # --- City
     world['index'] = ReaderSensor(keyword='world')
-    world_contains_city['forward'] = TorchEdgeReaderSensor(to='index', keyword='city', mode='forward')
+    city['index'] = ReaderSensor(keyword='city')
+    city[world_contains_city] = EdgeSensor(city['index'], world['index'], relation=world_contains_city, forward=lambda x, _: torch.ones_like(x).unsqueeze(-1))
 
 
-    def readNeighbors(links, current_neighbers, city1, city2):
-        if city1.getAttribute('index') in links[int(city2.getAttribute('index'))]:
+    def readNeighbors(index, data, arg1, arg2):
+        city1, city2 = arg1, arg2
+        if city1.getAttribute('index') in data[int(city2.getAttribute('index'))]:
             return True
         else:
             return False
 
-    neighbor['index'] = CandidateReaderSensor(keyword='links', forward=readNeighbors)
+    neighbor[city1.reversed, city2.reversed] = CompositionCandidateReaderSensor(city['index'], keyword='links', relations=(city1.reversed, city2.reversed), forward=readNeighbors)
 
     # --- Learners
-    city[firestationCity] = DummyCityLearner('index', edges=[world_contains_city['forward']])
+    city[firestationCity] = DummyCityLearner('index')
     
-    program = LearningBasedProgram(graph, model_helper(PoiModel, poi=[city[firestationCity], neighbor['index']]))
+    program = LearningBasedProgram(graph, model_helper(PoiModel, poi=[world, city, city[firestationCity], neighbor]))
     return program
 
 
diff --git a/test_regr/examples/graph_coloring/main2.py b/test_regr/examples/graph_coloring/main2.py
index 7fab8efb9..a3227d52c 100644
--- a/test_regr/examples/graph_coloring/main2.py
+++ b/test_regr/examples/graph_coloring/main2.py
@@ -6,8 +6,11 @@
 
 
 def model_declaration():
-    from regr.sensor.pytorch.sensors import ReaderSensor, TorchEdgeReaderSensor, ForwardEdgeSensor, ConstantSensor
-    from regr.sensor.pytorch.relation_sensors import CandidateSensor, CandidateReaderSensor
+    import torch
+
+    from regr.sensor.pytorch.sensors import ReaderSensor
+    from regr.sensor.pytorch.query_sensor import DataNodeReaderSensor
+    from regr.sensor.pytorch.relation_sensors import EdgeSensor, CompositionCandidateSensor
     from regr.program import LearningBasedProgram
     from regr.program.model.pytorch import PoiModel
 
@@ -18,30 +21,27 @@ def model_declaration():
 
     # --- City
     world['index'] = ReaderSensor(keyword='world')
-    world_contains_city['forward'] = TorchEdgeReaderSensor(to='index', keyword='city', mode='forward')
+    city['index'] = ReaderSensor(keyword='city')
+    city[world_contains_city] = EdgeSensor(city['index'], world['index'], relation=world_contains_city, forward=lambda x, _: torch.ones_like(x).unsqueeze(-1))
 
     # --- Neighbor
-    city1['backward'] = ForwardEdgeSensor('index', to='city1', mode='backward')
-    city2['backward'] = ForwardEdgeSensor('index', to='city2', mode='backward')
-    
-    def readCitylinks(datanodes_edges, datanode_concept1, datanode_concept2):
-        return True
-    
-    cityLink['index'] = CandidateSensor(forward=readCitylinks, edges=[city1['backward'], city2['backward']])
+    cityLink[city1.reversed, city2.reversed] = CompositionCandidateSensor(city['index'], relations=(city1.reversed, city2.reversed), forward=lambda *_, **__: True)
 
-    def readNeighbors(links, current_neighbers, city1, city2, _):
-        if city1.getAttribute('index') in links[int(city2.getAttribute('index'))]:
+    def readNeighbors(*_, data, datanode):
+        city1_node = datanode.relationLinks[city1.name][0]
+        city2_node = datanode.relationLinks[city2.name][0]
+        if city1_node.getAttribute('index') in data[int(city2_node.getAttribute('index'))]:
             return True
         else:
             return False
         
-    cityLink['neighbor'] = CandidateReaderSensor('index', keyword='links', forward=readNeighbors, edges=[city1['backward'], city2['backward']])
+    cityLink['neighbor'] = DataNodeReaderSensor(city1.reversed, city2.reversed, keyword='links', forward=readNeighbors)
 
     # --- Learners
-    city[firestationCity] = DummyCityLearner('index', edges=[world_contains_city['forward'], cityLink['neighbor']])
-    city[firestationCity] = ConstantSensor(data=None, label=True)
+    city[firestationCity] = DummyCityLearner('index')
+    # city[firestationCity] = ConstantSensor(data=None, label=True)
     
-    program = LearningBasedProgram(graph2, PoiModel)
+    program = LearningBasedProgram(graph2, PoiModel, poi=[world, city, cityLink])
     return program
 
 
diff --git a/test_regr/examples/graph_coloring/sensors.py b/test_regr/examples/graph_coloring/sensors.py
index 50e4a5211..82b5b629a 100644
--- a/test_regr/examples/graph_coloring/sensors.py
+++ b/test_regr/examples/graph_coloring/sensors.py
@@ -4,12 +4,9 @@
 import torch
 
 #  --- City
-class DummyCityLearner(TorchLearner):  # Learn Fire station classification for City
-    def forward(self,) -> Any:
-        result = torch.zeros(len(self.inputs[0]), 2)
-        
-        for t in result: # Initially all cities are firestation cities
-            t[1] = 1
-            t[0] = 0
-
+class DummyCityLearner(TorchLearner):
+    def forward(self, x):
+        result = torch.zeros(len(x), 2)
+        # Initially all cities are firestation cities
+        result[:, 1] = 1
         return result
diff --git a/test_regr/examples/nested_relations/graph.py b/test_regr/examples/nested_relations/graph.py
index a2e0c8f8d..68e66bea8 100644
--- a/test_regr/examples/nested_relations/graph.py
+++ b/test_regr/examples/nested_relations/graph.py
@@ -73,8 +73,7 @@
         # LC1 work_for has arg1 people and arg2 organization
         ifL(work_for, V(name='x'), andL(people, V(name='y', v=('x', rel_pair_phrase1.name)), organization, V(name='z', v=('x', rel_pair_phrase2.name))), V(match='x'))
             
-        # LC1bis if x is people and y is organization then they are in work_for relation
-        ifL(andL(people, V(name='x'), organization, V(name='y')), V(name='z'), work_for, V(match='z', v=(('x', rel_pair_phrase1.backward.name), ('y', rel_pair_phrase2.backward.name))))
+        # LC1bis if x is people and y is organization then they are in work_for relatio
 
         
         #LC3 each real phrase is either the same word starting and end with type arg1=arg2=Iword or two different words with arg1 is Bword and arg2 is Eword
diff --git a/test_regr/examples/nested_relations/test_main.py b/test_regr/examples/nested_relations/test_main.py
index 172b4d141..a0ed51490 100644
--- a/test_regr/examples/nested_relations/test_main.py
+++ b/test_regr/examples/nested_relations/test_main.py
@@ -125,8 +125,8 @@ def model_declaration(config, case):
 
     sentence['raw'] = TestSensor(expected_outputs=case.sentence.raw)
 
-    # Edge: sentence to word forward
-    word[rel_sentence_contains_word.forward, 'raw'] = TestSensor(
+    # Edge: sentence to word
+    word[rel_sentence_contains_word, 'raw'] = TestSensor(
         sentence['raw'],
         expected_inputs=(case.sentence.raw,),
         expected_outputs=(case.word.scw, case.word.raw))
@@ -172,28 +172,28 @@ def model_declaration(config, case):
         expected_inputs=(case.word.emb,),
         expected_outputs=case.word.Bword)
 
-    phrase[rel_phrase_word1.backward, rel_phrase_word2.backward] = TestSensor(
+    phrase[rel_phrase_word1.reversed, rel_phrase_word2.reversed] = TestSensor(
         word['emb'],
         expected_inputs=(case.word.emb,),
         expected_outputs=(case.phrase.pw1_backward, case.phrase.pw2_backward))
 
-    phrase[rel_sentence_contains_phrase.forward, 'raw'] = TestSensor(
+    phrase[rel_sentence_contains_phrase, 'raw'] = TestSensor(
         sentence['raw'],
         expected_inputs=(case.sentence.raw,),
         expected_outputs=(case.phrase.scw, case.phrase.raw))
     
     phrase['emb'] = TestSensor(
-        rel_phrase_word1.backward('emb'), rel_phrase_word2.backward('emb'),
+        rel_phrase_word1.reversed('emb'), rel_phrase_word2.reversed('emb'),
         expected_inputs=(case.phrase.emb[:, :2048], case.phrase.emb[:, 2048:]),
         expected_outputs=case.phrase.emb)
 
-    pair[rel_pair_phrase1.backward, rel_pair_phrase2.backward] = TestSensor(
+    pair[rel_pair_phrase1.reversed, rel_pair_phrase2.reversed] = TestSensor(
         phrase['emb'],
         expected_inputs=(case.phrase.emb,),
         expected_outputs=(case.pair.pa1_backward, case.pair.pa2_backward))
 
     pair['emb'] = TestSensor(
-        rel_pair_phrase1.backward('emb'), rel_pair_phrase2.backward('emb'),
+        rel_pair_phrase1.reversed('emb'), rel_pair_phrase2.reversed('emb'),
         expected_inputs=(case.pair.emb[:,:4096],case.pair.emb[:,4096:]),
         expected_outputs=case.pair.emb)
 
diff --git a/test_regr/sensor/pytorch/sensors.py b/test_regr/sensor/pytorch/sensors.py
index 2621eb174..8a2d7e602 100644
--- a/test_regr/sensor/pytorch/sensors.py
+++ b/test_regr/sensor/pytorch/sensors.py
@@ -2,7 +2,7 @@
 import torch
 import numpy as np
 
-from regr.sensor.pytorch.sensors import FunctionalSensor, ConstantSensor, ConstantEdgeSensor, JointSensor
+from regr.sensor.pytorch.sensors import FunctionalSensor, ConstantSensor, JointSensor
 from regr.sensor.pytorch.relation_sensors import EdgeSensor
 from regr.sensor.pytorch.learners import TorchLearner
 
diff --git a/test_regr/sensor/pytorch/test_candidate_reader_sensor.py b/test_regr/sensor/pytorch/test_candidate_reader_sensor.py
index 00e9636d9..285f8ff6c 100644
--- a/test_regr/sensor/pytorch/test_candidate_reader_sensor.py
+++ b/test_regr/sensor/pytorch/test_candidate_reader_sensor.py
@@ -31,8 +31,7 @@ def case():
 
 @pytest.fixture()
 def graph(case):
-    from regr.sensor.pytorch.sensors import ReaderSensor, TorchEdgeReaderSensor
-    from regr.sensor.pytorch.query_sensor import InstantiateSensor
+    from regr.sensor.pytorch.sensors import ReaderSensor
     from regr.graph import Graph, Concept, Relation, Property
 
     from .sensors import TestSensor, TestEdgeSensor
@@ -53,10 +52,9 @@ def graph(case):
 
     # model
     container['raw'] = ReaderSensor(keyword='container_keyword')
-    concept[container_contains_concept.forward] = TestEdgeSensor(
+    concept[container_contains_concept] = TestEdgeSensor(
         container['raw'],
         relation=container_contains_concept,
-        mode='forward',
         expected_inputs=(case.container,),
         expected_outputs=case.container_edge)
     concept['raw'] = TestSensor(
@@ -77,28 +75,28 @@ def sensor(case, graph):
     (edge_concept1, edge_concept2,) = edge.has_a()
 
     collector = []
-    def forward(datanode_concept1, datanode_concept2, constant, concept_raw, data):
+    def forward(constant, concept_raw, data, arg1, arg2):
         # update collector
         idx = len(collector)
-        collector.append((datanode_concept1, datanode_concept2))
+        collector.append((arg1, arg2))
         # test concept 1
-        assert isinstance(datanode_concept1, DataNode)
-        assert datanode_concept1.getOntologyNode() == concept
+        assert isinstance(arg1, DataNode)
+        assert arg1.getOntologyNode() == concept
         # test concept 2
-        assert isinstance(datanode_concept2, DataNode)
-        assert datanode_concept2.getOntologyNode() == concept
+        assert isinstance(arg2, DataNode)
+        assert arg2.getOntologyNode() == concept
         # other arguments are like functional sensor
         assert constant == case.constant
-        index1 = datanode_concept1.getAttribute('raw')
-        index2 = datanode_concept2.getAttribute('raw')
+        index1 = arg1.getAttribute('raw')
+        index2 = arg2.getAttribute('raw')
         return data[index1][index2]
     sensor = CompositionCandidateReaderSensor(
         case.constant,
         concept['raw'],
-        relations=(edge_concept1.backward, edge_concept2.backward),
+        relations=(edge_concept1.reversed, edge_concept2.reversed),
         forward=forward,
         keyword='edge_keyword')
-    edge[edge_concept1.backward, edge_concept2.backward] = sensor
+    edge[edge_concept1.reversed, edge_concept2.reversed] = sensor
     return sensor
 
 @pytest.fixture()
diff --git a/test_regr/sensor/pytorch/test_candidate_sensor.py b/test_regr/sensor/pytorch/test_candidate_sensor.py
index 28b30506f..9c7cd2c18 100644
--- a/test_regr/sensor/pytorch/test_candidate_sensor.py
+++ b/test_regr/sensor/pytorch/test_candidate_sensor.py
@@ -31,8 +31,7 @@ def case():
 
 @pytest.fixture()
 def graph(case):
-    from regr.sensor.pytorch.sensors import ReaderSensor, TorchEdgeReaderSensor
-    from regr.sensor.pytorch.query_sensor import InstantiateSensor
+    from regr.sensor.pytorch.sensors import ReaderSensor
     from regr.graph import Graph, Concept, Relation, Property
 
     from .sensors import TestSensor, TestEdgeSensor
@@ -53,10 +52,9 @@ def graph(case):
 
     # model
     container['raw'] = ReaderSensor(keyword='container_keyword')
-    concept[container_contains_concept.forward] = TestEdgeSensor(
+    concept[container_contains_concept] = TestEdgeSensor(
         container['raw'],
         relation=container_contains_concept,
-        mode='forward',
         expected_inputs=(case.container,),
         expected_outputs=case.container_edge)
     concept['raw'] = TestSensor(
@@ -77,28 +75,28 @@ def sensor(case, graph):
     (edge_concept1, edge_concept2,) = edge.has_a()
 
     collector = []
-    def forward(datanode_concept1, datanode_concept2, constant, *_):
+    def forward(constant, raw, arg1, arg2):
         # update collector
         idx = len(collector)
         assert idx < 4
-        collector.append((datanode_concept1, datanode_concept2))
+        collector.append((arg1, arg2))
         # test concept 1
-        assert isinstance(datanode_concept1, DataNode)
-        assert datanode_concept1.getOntologyNode() == concept
+        assert isinstance(arg1, DataNode)
+        assert arg1.getOntologyNode() == concept
         # test concept 2
-        assert isinstance(datanode_concept2, DataNode)
-        assert datanode_concept2.getOntologyNode() == concept
+        assert isinstance(arg2, DataNode)
+        assert arg2.getOntologyNode() == concept
         # other arguments are like functional sensor
         assert constant == case.constant
-        index1 = datanode_concept1.getAttribute('raw')
-        index2 = datanode_concept2.getAttribute('raw')
+        index1 = arg1.getAttribute('raw')
+        index2 = arg2.getAttribute('raw')
         return case.edge[index1][index2]
     sensor = CompositionCandidateSensor(
         case.constant,
         concept['raw'],
-        relations=(edge_concept1.backward, edge_concept2.backward),
+        relations=(edge_concept1.reversed, edge_concept2.reversed),
         forward=forward)
-    edge[edge_concept1.backward, edge_concept2.backward] = sensor
+    edge[edge_concept1.reversed, edge_concept2.reversed] = sensor
     return sensor
 
 
diff --git a/test_regr/sensor/pytorch/test_datanode_sensor.py b/test_regr/sensor/pytorch/test_datanode_sensor.py
index bb77fcf64..795e9325a 100644
--- a/test_regr/sensor/pytorch/test_datanode_sensor.py
+++ b/test_regr/sensor/pytorch/test_datanode_sensor.py
@@ -26,7 +26,7 @@ def case():
 
 @pytest.fixture()
 def graph(case):
-    from regr.sensor.pytorch.sensors import ReaderSensor, TorchEdgeReaderSensor
+    from regr.sensor.pytorch.sensors import ReaderSensor
     from regr.graph import Graph, Concept, Relation
 
     from .sensors import TestSensor, TestEdgeSensor
@@ -43,10 +43,9 @@ def graph(case):
 
     # model
     container['raw'] = ReaderSensor(keyword='container_keyword')
-    concept[container_contains_concept.forward] = TestEdgeSensor(
+    concept[container_contains_concept] = TestEdgeSensor(
         container['raw'],
         relation=container_contains_concept,
-        mode='forward',
         expected_inputs=(case.container,),
         expected_outputs=case.container_edge)
     concept['raw'] = TestSensor(
@@ -73,7 +72,7 @@ def sensor(case, graph):
     concept = graph['sub/concept']
 
     datanodes = []
-    def forward(datanode, reader1, reader2, constant):
+    def forward(reader1, reader2, constant, datanode):
         idx = len(datanodes)
         assert idx < 2
         datanodes.append(datanode)
diff --git a/test_regr/sensor/pytorch/test_query_sensor.py b/test_regr/sensor/pytorch/test_query_sensor.py
index 1758b908e..5a1fee2b1 100644
--- a/test_regr/sensor/pytorch/test_query_sensor.py
+++ b/test_regr/sensor/pytorch/test_query_sensor.py
@@ -7,10 +7,10 @@ def case():
     import random
 
     case = {
-        'reader1': 'hello world, {}'.format(random.random()),
-        'reader2': 'hello world, {}'.format(random.random()),
+        'reader1': ['hello world, {}'.format(random.random())],
+        'reader2': ['hello world, {}'.format(random.random())],
         'constant': random.random(),
-        'output': 'output, {}'.format(random.random())}
+        'output': ['output, {}'.format(random.random())]}
     case = Namespace(case)
     return case
 
@@ -41,7 +41,7 @@ def sensor(case, graph):
 
     concept = graph['sub/concept']
 
-    def forward(datanodes, reader1, reader2, constant):
+    def forward(reader1, reader2, constant, datanodes):
         # datanodes is a list of datanode
         assert len(datanodes) == 1
         assert isinstance(datanodes[0], DataNode)