Source code for pm4py.algo.anonymization.trace_variant_query.util.behavioralAppropriateness
'''
PM4Py – A Process Mining Library for Python
Copyright (C) 2024 Process Intelligence Solutions UG (haftungsbeschränkt)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see this software project's root or
visit <https://www.gnu.org/licenses/>.
Website: https://processintelligence.solutions
Contact: info@processintelligence.solutions
'''
caseIDKey = "Case ID"
activityKey = "Activity"
durationKey = "Complete Timestamp"
[docs]
def getFollowersOfEventInTrace(event, trace):
followers = list()
if event not in trace:
return followers
eventIndex = trace.index(event)
restTrace = trace[eventIndex + 1:]
for e in restTrace:
if e not in followers:
followers.append(e)
return followers
[docs]
def getFollowsRelations(allEvents, traces):
followsMatrix = {}
alwaysCtr = 0
sometimesCtr = len(allEvents) * len(allEvents) # In the beginning, all relations are 'Sometimes'.
neverCtr = 0
for event in allEvents:
alwaysFollows = allEvents.copy()
neverFollows = allEvents.copy()
for eClmn in allEvents:
followsMatrix[(event, eClmn)] = 'S'
for trace in traces:
if event in trace:
followers = getFollowersOfEventInTrace(event=event, trace=trace)
for f in followers:
if f in neverFollows:
neverFollows.remove(f)
for e in allEvents:
if e not in followers:
if e in alwaysFollows:
alwaysFollows.remove(e)
for a in alwaysFollows:
followsMatrix[(event, a)] = 'A'
sometimesCtr -= 1
alwaysCtr += 1
for n in neverFollows:
followsMatrix[(event, n)] = 'N'
sometimesCtr -= 1
neverCtr += 1
return followsMatrix
[docs]
def getPrecedesRelations(allEvents, traces):
precedesMatrix = {}
alwaysCtr = 0
sometimesCtr = len(allEvents) * len(allEvents) # In the beginning, all relations are 'Sometimes'.
neverCtr = 0
for event in allEvents:
alwaysPrecedes = allEvents.copy()
neverPrecedes = allEvents.copy()
for eClmn in allEvents: # eClmn -> event in respective column
precedesMatrix[(event, eClmn)] = 'S'
for trace in traces:
if event in trace:
predecessors = getPredecessorsOfEventInTrace(event=event, trace=trace)
for p in predecessors:
if p in neverPrecedes:
neverPrecedes.remove(p)
for e in allEvents:
if e not in predecessors:
if e in alwaysPrecedes:
alwaysPrecedes.remove(e)
for a in alwaysPrecedes:
precedesMatrix[(event, a)] = 'A'
sometimesCtr -= 1
alwaysCtr += 1
for n in neverPrecedes:
precedesMatrix[(event, n)] = 'N'
sometimesCtr -= 1
neverCtr += 1
return precedesMatrix
[docs]
def getPredecessorsOfEventInTrace(event, trace):
predecessors = list()
if event not in trace:
return predecessors
eventIndex = max(loc for loc, val in enumerate(trace) if val == event)
restTrace = trace[:eventIndex]
for e in restTrace:
if e not in predecessors:
predecessors.append(e)
return predecessors
[docs]
def getBARelations(traces, events):
followsRelations = getFollowsRelations(allEvents=events, traces=traces)
precedesRelations = getPrecedesRelations(allEvents=events, traces=traces)
return followsRelations, precedesRelations
# checks whether a prefix conforms to the log overall Behavioral Appropriateness given by precedes and follows Relations
# return [true/false]
[docs]
def getBAViolations(allEvents, followsRelations, precedesRelations, prefix, TRACE_END):
violationsCtr = 0
logEvents = allEvents.copy()
logEvents.remove(TRACE_END) # all events except for TRACE_END. used to look up 'always follows' relations
localPrefix = list() # prefix where TRACE_END is removed. used to obtain followers and predecessors of events contained
prefixEvents = list() # all events in the prefix (except for TRACE_END)
for e in prefix:
if e != TRACE_END:
localPrefix.append(e)
if e not in prefixEvents:
prefixEvents.append(e)
followersDict = dict()
for event in prefixEvents: # get followers for each event in the prefix and store them
followersOfEvent = getFollowersOfEventInTrace(event=event, trace=localPrefix)
followersDict[event] = followersOfEvent
predecessorsDict = dict()
for event in prefixEvents: # get predecessors for each event in the prefix and store them
predecessorsOfEvent = getPredecessorsOfEventInTrace(event=event, trace=localPrefix)
predecessorsDict[event] = predecessorsOfEvent
for e1 in prefixEvents:
for follower in followersDict[e1]: # check if 'never follows' relations are violated
if followsRelations[(e1, follower)] == 'N':
violationsCtr += 1
for predecessor in predecessorsDict[e1]: # check if 'never precedes' relations are violated
if precedesRelations[(e1, predecessor)] == 'N':
violationsCtr += 1
for e2 in logEvents: # to check if 'always follows' is violated, all events must be checked
if TRACE_END in prefix: # no TRACE_END in prefix -> event saving the always follows relation could follow sometime later
if followsRelations[
(e1, e2)] == 'A': # followsRelations[(e1, e2)] == 'A' -> if e1 in trace, e2 always follows
if e2 not in followersDict[e1]:
violationsCtr += 1
if precedesRelations[
(e1, e2)] == 'A': # precedesRelations[(e1, e2)] == 'A' -> if e1 in trace, e2 always precedes
if e2 not in predecessorsDict[
e1]: # if e1 in trace and e2 does not precede right now it won't ever do -> no need to wait for TRACE_END
violationsCtr += 1
return violationsCtr # if no {always/sometimes} {follows/precedes} relation is violated, return 0