Repository: DARMA-tasking/LB-analysis-framework

Issue:
Add user-defined fields to JSON parser, create Python data structures of objects that hold this info
We have added a new region, `user_defined`, to the JSON files. Please make the parser read this data into Python data structures. Here is an example set of json files with this data: [fake-4x-block-overdecomp-3-uncompressed.zip](https://github.com/DARMA-tasking/LB-analysis-framework/files/8534118/fake-4x-block-overdecomp-3-uncompressed.zip).

Discussion:

Patch:
diff --git a/src/lbaf/IO/lbsVTDataReader.py b/src/lbaf/IO/lbsVTDataReader.py
index 215e62e..a1f5770 100644
--- a/src/lbaf/IO/lbsVTDataReader.py
+++ b/src/lbaf/IO/lbsVTDataReader.py
@@ -189,11 +189,12 @@ class LoadReader:
             for task in phase["tasks"]:
                 task_time = task.get("time")
                 task_object_id = task.get("entity").get("id")
+                task_used_defined = task.get("user_defined")
 
                 # Update rank if iteration was requested
                 if phase_ids in (phase_id, -1):
                     # Instantiate object with retrieved parameters
-                    obj = Object(task_object_id, task_time, node_id)
+                    obj = Object(task_object_id, task_time, node_id, user_defined=task_used_defined)
                     # If this iteration was never encountered initialize rank object
                     returned_dict.setdefault(phase_id, Rank(node_id, logger=self.__logger))
                     # Add object to rank
diff --git a/src/lbaf/IO/schemaValidator.py b/src/lbaf/IO/schemaValidator.py
index cc8c3e3..661bf5a 100644
--- a/src/lbaf/IO/schemaValidator.py
+++ b/src/lbaf/IO/schemaValidator.py
@@ -35,7 +35,8 @@ class SchemaValidator:
                                         'time': float,
                                     }
                                 ],
-                                'time': float
+                                'time': float,
+                                Optional('user_defined'): dict
                             },
                         ],
                         Optional('communications'): [
diff --git a/src/lbaf/Model/lbsObject.py b/src/lbaf/Model/lbsObject.py
index 8129be3..a7611ad 100644
--- a/src/lbaf/Model/lbsObject.py
+++ b/src/lbaf/Model/lbsObject.py
@@ -4,7 +4,7 @@ from .lbsObjectCommunicator import ObjectCommunicator
 class Object:
     """ A class representing an object with time and communicator
     """
-    def __init__(self, i: int, t: float, p: int = None, c: ObjectCommunicator = None):
+    def __init__(self, i: int, t: float, p: int = None, c: ObjectCommunicator = None, user_defined: dict = None):
         # Object index
         if not isinstance(i, int) or isinstance(i, bool):
             raise TypeError(f"i: {i} is type of {type(i)}! Must be <class 'int'>!")
@@ -29,6 +29,12 @@ class Object:
         else:
             raise TypeError(f"c: {c} is type of {type(c)}! Must be <class 'ObjectCommunicator'>!")
 
+        # User defined fields
+        if isinstance(user_defined, dict) or user_defined is None:
+            self.__user_defined = user_defined
+        else:
+            raise TypeError(f"user_defined: {user_defined} is type of {type(user_defined)}! Must be <class 'dict'>!")
+
     def __repr__(self):
         return f"Object id: {self.__index}, time: {self.__time}"
 


Test patch:
diff --git a/tests/data/synthetic_lb_stats_wrong_schema/schema_error.txt b/tests/data/synthetic_lb_stats_wrong_schema/schema_error.txt
index a0cc7ff..73c9251 100644
--- a/tests/data/synthetic_lb_stats_wrong_schema/schema_error.txt
+++ b/tests/data/synthetic_lb_stats_wrong_schema/schema_error.txt
@@ -1,3 +1,3 @@
 Key 'phases' error:
-Or({'id': <class 'int'>, 'tasks': [{'entity': {Optional('collection_id'): <class 'int'>, 'home': <class 'int'>, 'id': <class 'int'>, Optional('index'): [<class 'int'>], 'type': <class 'str'>, 'migratable': <class 'bool'>, Optional('objgroup_id'): <class 'int'>}, 'node': <class 'int'>, 'resource': <class 'str'>, Optional('subphases'): [{'id': <class 'int'>, 'time': <class 'float'>}], 'time': <class 'float'>}], Optional('communications'): [{'type': <class 'str'>, 'to': {'type': <class 'str'>, 'id': <class 'int'>, Optional('home'): <class 'int'>, Optional('collection_id'): <class 'int'>, Optional('migratable'): <class 'bool'>, Optional('index'): [<class 'int'>], Optional('objgroup_id'): <class 'int'>}, 'messages': <class 'int'>, 'from': {'type': <class 'str'>, 'id': <class 'int'>, Optional('home'): <class 'int'>, Optional('collection_id'): <class 'int'>, Optional('migratable'): <class 'bool'>, Optional('index'): [<class 'int'>], Optional('objgroup_id'): <class 'int'>}, 'bytes': <class 'float'>}]}) did not validate {'id': 0, 'tasks1': [{'entity': {'id': 0, 'type': 'object', 'migratable': True}, 'node': 0, 'resource': 'cpu', 'time': 1.0}, {'entity': {'id': 1, 'type': 'object', 'migratable': True}, 'node': 0, 'resource': 'cpu', 'time': 0.5}, {'entity': {'id': 2, 'type': 'object', 'migratable': True}, 'node': 0, 'resource': 'cpu', 'time': 0.5}, {'entity': {'id': 3, 'type': 'object', 'migratable': True}, 'node': 0, 'resource': 'cpu', 'time': 0.5}], 'communications': [{'type': 'SendRecv', 'to': {'type': 'object', 'id': 5}, 'messages': 1, 'from': {'type': 'object', 'id': 0}, 'bytes': 2.0}, {'type': 'SendRecv', 'to': {'type': 'object', 'id': 4}, 'messages': 1, 'from': {'type': 'object', 'id': 1}, 'bytes': 1.0}, {'type': 'SendRecv', 'to': {'type': 'object', 'id': 2}, 'messages': 1, 'from': {'type': 'object', 'id': 3}, 'bytes': 1.0}, {'type': 'SendRecv', 'to': {'type': 'object', 'id': 8}, 'messages': 1, 'from': {'type': 'object', 'id': 3}, 'bytes': 0.5}]}
+Or({'id': <class 'int'>, 'tasks': [{'entity': {Optional('collection_id'): <class 'int'>, 'home': <class 'int'>, 'id': <class 'int'>, Optional('index'): [<class 'int'>], 'type': <class 'str'>, 'migratable': <class 'bool'>, Optional('objgroup_id'): <class 'int'>}, 'node': <class 'int'>, 'resource': <class 'str'>, Optional('subphases'): [{'id': <class 'int'>, 'time': <class 'float'>}], 'time': <class 'float'>, Optional('user_defined'): <class 'dict'>}], Optional('communications'): [{'type': <class 'str'>, 'to': {'type': <class 'str'>, 'id': <class 'int'>, Optional('home'): <class 'int'>, Optional('collection_id'): <class 'int'>, Optional('migratable'): <class 'bool'>, Optional('index'): [<class 'int'>], Optional('objgroup_id'): <class 'int'>}, 'messages': <class 'int'>, 'from': {'type': <class 'str'>, 'id': <class 'int'>, Optional('home'): <class 'int'>, Optional('collection_id'): <class 'int'>, Optional('migratable'): <class 'bool'>, Optional('index'): [<class 'int'>], Optional('objgroup_id'): <class 'int'>}, 'bytes': <class 'float'>}]}) did not validate {'id': 0, 'tasks1': [{'entity': {'id': 0, 'type': 'object', 'migratable': True}, 'node': 0, 'resource': 'cpu', 'time': 1.0}, {'entity': {'id': 1, 'type': 'object', 'migratable': True}, 'node': 0, 'resource': 'cpu', 'time': 0.5}, {'entity': {'id': 2, 'type': 'object', 'migratable': True}, 'node': 0, 'resource': 'cpu', 'time': 0.5}, {'entity': {'id': 3, 'type': 'object', 'migratable': True}, 'node': 0, 'resource': 'cpu', 'time': 0.5}], 'communications': [{'type': 'SendRecv', 'to': {'type': 'object', 'id': 5}, 'messages': 1, 'from': {'type': 'object', 'id': 0}, 'bytes': 2.0}, {'type': 'SendRecv', 'to': {'type': 'object', 'id': 4}, 'messages': 1, 'from': {'type': 'object', 'id': 1}, 'bytes': 1.0}, {'type': 'SendRecv', 'to': {'type': 'object', 'id': 2}, 'messages': 1, 'from': {'type': 'object', 'id': 3}, 'bytes': 1.0}, {'type': 'SendRecv', 'to': {'type': 'object', 'id': 8}, 'messages': 1, 'from': {'type': 'object', 'id': 3}, 'bytes': 0.5}]}
 Missing key: 'tasks'
\ No newline at end of file