diff --git a/Assignment-2/DCR_graph.py b/Assignment-2/DCR_graph.py new file mode 100644 index 0000000..ad3a8a5 --- /dev/null +++ b/Assignment-2/DCR_graph.py @@ -0,0 +1,218 @@ +from __future__ import annotations +from enum import Enum + +import xmltodict + +def listify(element): + if isinstance(element, list): + return element + return [element] + +class Event(): + def __init__(self, _id:str, name:str, id_dict:dict, parent:Process=None) -> None: + self._id = _id + id_dict[_id] = self + self.name = name.lower() + self.pending = False + self.executed = False + self.included = False + self.relations_to : list[Relationship] = [] + self.relations_from : list[Relationship] = [] + self.parent = parent + + def execute(self): + self.executed = True + self.pending = False + for relationship in self.relations_from: + relationship.execute() + + @property + def enabled(self): + if self.parent is not None: + included = self.included and self.parent.enabled + else: + included = self.included + + no_conditions = all( + condition.source.executed or not condition.source.included + for condition in [ + relation + for relation in self.relations_to + if relation.type == RelationsshipType.condition + ] + ) + no_milestones = all( + not milestone.source.pending or not milestone.source.included + for milestone in [ + relation + for relation in self.relations_to + if relation.type == RelationsshipType.milestone + ] + ) + + return included and no_conditions and no_milestones + + def enabled_list(self): + if self.included: + return [self] + else: + return [] + + def __repr__(self) -> str: + return self.name + + def pending_list(self): + if self.pending and self.included: + return [self] + else: + return [] + +class Process(Event): + def __init__(self, _id:str, name:str, label_mappings: dict, events: list, id_dict: dict,parent:Process=None) -> None: + super().__init__(_id,name,id_dict,parent) + self.process_process(label_mappings, events, id_dict) + + def process_process(self, label_mappings: dict, events: list, id_dict): + self.events = [] + for event in events: + _id = event["@id"] + label = label_mappings[_id] + if "@type" in event: + new_event = Process(_id, label, label_mappings, listify(event["event"]), id_dict, self) + else: + new_event = Event(_id, label, id_dict, self) + + self.events.append(new_event) + + def enabled_list(self): + if self.enabled: + enabled_events = [self] if self._id != "" else [] + for event in self.events: + enabled_events += event.enabled_list() + else: + enabled_events = [] + + return enabled_events + + def pending_list(self): + pending_events = [] + if self.pending and self.included: + pending_events.append(self) + + for event in self.events: + pending_events += event.pending_list() + + return pending_events + +class RelationsshipType(Enum): + condition = 0 + response = 1 + coresponse = 2 + exclude = 3 + include = 4 + milestone = 5 + update = 6 + spawn = 7 + templateSpawn = 8 + +class Relationship(): + def __init__(self, source:Event, target:Event, type) -> None: + self.source = source + self.target = target + self.type = type + + def execute(self): + if self.type == RelationsshipType.condition: + pass # does nothing + elif self.type == RelationsshipType.response: + self.target.pending = True + elif self.type == RelationsshipType.coresponse: + pass # Don't know what this one does + elif self.type == RelationsshipType.exclude: + self.target.included = False + elif self.type == RelationsshipType.include: + self.target.included = True + elif self.type == RelationsshipType.milestone: + pass # does nothing + elif self.type == RelationsshipType.update: + pass + elif self.type == RelationsshipType.spawn: + pass + elif self.type == RelationsshipType.templateSpawn: + pass + +class Graph(): + def __init__(self, process:Process, relationships:list[Relationship], id_dict: dict) -> None: + self.process = process + self.relationships = relationships + self.id_dict = id_dict + + @property + def enabled(self): + return self.process.enabled_list() + + @property + def pending(self): + return self.process.pending_list() + +def xml_to_dcr(xml_file): + with open(xml_file) as file_pointer: + dcr_dict = xmltodict.parse(file_pointer.read())["dcrgraph"] + + label_mappings = { + lm["@eventId"]:lm["@labelId"] + for lm in listify(dcr_dict["specification"]["resources"]["labelMappings"]["labelMapping"]) + } + + id_dict: dict[str,Event] = {} + graph = Process("", "", label_mappings, listify(dcr_dict["specification"]["resources"]["events"]["event"]), id_dict) + graph.included = True + + def extract_markings(key): + return [ + _id["@id"] + for _id in listify(dcr_dict["runtime"]["marking"][key]["event"]) + ] if dcr_dict["runtime"]["marking"][key] is not None else [] + + executed = extract_markings("executed") + for _id in executed: + id_dict[_id].executed = True + + included = extract_markings("included") + for _id in included: + id_dict[_id].included = True + + pending = extract_markings("pendingResponses") + for _id in pending: + id_dict[_id].pending = True + + + def extract_relationships(key): + return [ + (r["@sourceId"], r["@targetId"]) + for r in listify(dcr_dict["specification"]["constraints"][key][key[:-1]]) + ] if dcr_dict["specification"]["constraints"][key] is not None else [] + + conditions = extract_relationships("conditions") + responses = extract_relationships("responses") + coresponses = extract_relationships("coresponses") + excludes = extract_relationships("excludes") + includes = extract_relationships("includes") + milestones = extract_relationships("milestones") + updates = extract_relationships("updates") + spawns = extract_relationships("spawns") + templateSpawns = extract_relationships("templateSpawns") + + relationships: list[Relationship] = [] + + for i, relationship_list in enumerate([conditions,responses,coresponses,excludes,includes,milestones,updates,spawns,templateSpawns]): + for relationship in relationship_list: + source = id_dict[relationship[0]] + target = id_dict[relationship[1]] + relationships.append(Relationship(source,target, RelationsshipType(i))) + + for relationship in relationships: + relationship.source.relations_from.append(relationship) + relationship.target.relations_to.append(relationship) + + return Graph(graph, relationship, id_dict) diff --git a/Assignment-2/__pycache__/DCR_graph.cpython-310.pyc b/Assignment-2/__pycache__/DCR_graph.cpython-310.pyc new file mode 100644 index 0000000..3c6ee32 Binary files /dev/null and b/Assignment-2/__pycache__/DCR_graph.cpython-310.pyc differ diff --git a/Assignment-2/__pycache__/conformance_testing.cpython-310.pyc b/Assignment-2/__pycache__/conformance_testing.cpython-310.pyc new file mode 100644 index 0000000..157a03a Binary files /dev/null and b/Assignment-2/__pycache__/conformance_testing.cpython-310.pyc differ diff --git a/Assignment-2/__pycache__/log.cpython-310.pyc b/Assignment-2/__pycache__/log.cpython-310.pyc new file mode 100644 index 0000000..da85df8 Binary files /dev/null and b/Assignment-2/__pycache__/log.cpython-310.pyc differ diff --git a/Assignment-2/conformance_testing.py b/Assignment-2/conformance_testing.py new file mode 100644 index 0000000..ec91823 --- /dev/null +++ b/Assignment-2/conformance_testing.py @@ -0,0 +1,28 @@ +import pandas as pd + +from DCR_graph import Graph + +def conformance_test(log:pd.DataFrame, dcr_graph:Graph): + all_event_names = { + value.name:value for value in dcr_graph.id_dict.values() + } + + log = log.sort_values(by="Date") + + for _, event in log.iterrows(): + event_name = event.EventName.lower() + if event_name not in all_event_names: + if "_ROW_" not in all_event_names: + return False + + event_name = "_ROW_" + + if not all_event_names[event_name].enabled: + return False + + all_event_names[event_name].execute() + + if dcr_graph.pending != []: + return False + + return True diff --git a/Assignment-2/data/DCR-assignment2.xml b/Assignment-2/data/DCR-assignment2.xml index 9552ff5..52d0350 100644 --- a/Assignment-2/data/DCR-assignment2.xml +++ b/Assignment-2/data/DCR-assignment2.xml @@ -632,8 +632,6 @@ - - diff --git a/Assignment-2/log.py b/Assignment-2/log.py new file mode 100644 index 0000000..11529ec --- /dev/null +++ b/Assignment-2/log.py @@ -0,0 +1,7 @@ +import pandas as pd + +def read_log(log_file): + data = pd.read_csv(log_file, delimiter=";") + grouped = data.groupby(data.ID) + + return grouped \ No newline at end of file diff --git a/Assignment-2/main.py b/Assignment-2/main.py new file mode 100644 index 0000000..f1efa7a --- /dev/null +++ b/Assignment-2/main.py @@ -0,0 +1,28 @@ +""" +Usage: + main.py DCR LOG + +Options: + DCR The DCR graph in xml format + LOG The log in csv format +""" +import copy + +from docopt import docopt + +from DCR_graph import xml_to_dcr +from log import read_log +from conformance_testing import conformance_test + +def main(): + arguments = docopt(__doc__) + graph = xml_to_dcr(arguments["DCR"]) + logs = read_log(arguments["LOG"]) + + tests = [conformance_test(trace[1], copy.deepcopy(graph)) for trace in logs] + print("Success: ", tests.count(True)) + print("Failure: ", tests.count(False)) + + +if __name__ == "__main__": + main()