pm4js
Version:
Process Mining for Javascript
1,270 lines (1,224 loc) • 37.8 kB
JavaScript
class InductiveMiner {
static applyPlugin(eventLog, activityKey="concept:name", threshold=0.0, removeNoise=false) {
return InductiveMiner.apply(eventLog, activityKey, threshold, null, removeNoise);
}
static applyPluginDFG(frequencyDfg, activityKey="concept:name", threshold=0.0, removeNoise=false) {
return InductiveMiner.apply(null, activityKey, threshold, frequencyDfg, removeNoise);
}
static applyDfg(frequencyDfg, threshold=0.0, removeNoise=false) {
return InductiveMiner.apply(null, null, threshold, frequencyDfg, removeNoise);
}
static apply(eventLog, activityKey="concept:name", threshold=0.0, freqDfg=null, removeNoise=false, variantsFiltering=false) {
if (variantsFiltering) {
if (threshold > 0.0) {
let variants = GeneralLogStatistics.getVariants(eventLog);
let variants2 = [];
let totalCount = 0;
for (let k in variants) {
let v = variants[k];
variants2.push([k, v]);
totalCount += 1;
}
variants2.sort((a, b) => b[1] - a[1]);
let includedVariants = [];
let i = 0;
let partialSum = 0;
while (i < variants2.length) {
if (partialSum <= totalCount * (1 - threshold)) {
includedVariants.push(variants2[i][0]);
}
partialSum += variants2[i][1];
i++;
}
eventLog = LogGeneralFiltering.filterVariants(eventLog, includedVariants);
}
}
let tree = InductiveMiner.inductiveMiner(eventLog, null, activityKey, removeNoise, threshold, freqDfg);
if (eventLog == null) {
Pm4JS.registerObject(tree, "Process Tree (Inductive Miner DFG)");
}
else {
Pm4JS.registerObject(tree, "Process Tree (Inductive Miner)");
}
return tree;
}
static keepOneTracePerVariant(log, activityKey) {
let newEventLog = new EventLog();
let variants = GeneralLogStatistics.getVariants(log, activityKey);
for (let vari in variants) {
let activ = vari.split(",");
let newTrace = new Trace();
for (let act of activ) {
if (act.length > 0) {
let newEvent = new Event();
newEvent.attributes[activityKey] = new Attribute(act);
newTrace.events.push(newEvent);
}
}
newEventLog.traces.push(newTrace);
}
return newEventLog;
}
static inductiveMiner(log, treeParent, activityKey, removeNoise, threshold, freqDfg=null, skippable=false) {
if (log != null) {
if (threshold == 0) {
log = InductiveMiner.keepOneTracePerVariant(log, activityKey);
}
freqDfg = FrequencyDfgDiscovery.apply(log, activityKey);
if (threshold > 0 && removeNoise) {
freqDfg = InductiveMiner.removeNoiseFromDfg(freqDfg, threshold);
}
let emptyTraces = InductiveMiner.countEmptyTraces(log);
if (emptyTraces > threshold * log.traces.length) {
let xor = new ProcessTree(treeParent, ProcessTreeOperator.EXCLUSIVE, null);
let skip = new ProcessTree(xor, null, null);
xor.children.push(InductiveMiner.inductiveMiner(InductiveMiner.filterNonEmptyTraces(log), xor, activityKey, false, threshold));
xor.children.push(skip);
return xor;
}
}
else if (threshold > 0 && removeNoise) {
freqDfg = InductiveMiner.removeNoiseFromDfg(freqDfg, threshold);
}
if (skippable) {
let xor = new ProcessTree(treeParent, ProcessTreeOperator.EXCLUSIVE, null);
let skip = new ProcessTree(xor, null, null);
xor.children.push(InductiveMiner.inductiveMiner(null, xor, activityKey, false, threshold, freqDfg));
xor.children.push(skip);
return xor;
}
if (Object.keys(freqDfg.pathsFrequency).length == 0) {
return InductiveMiner.baseCase(freqDfg, treeParent);
}
let detectedCut = InductiveMiner.detectCut(log, freqDfg, treeParent, activityKey, threshold);
if (detectedCut != null) {
return detectedCut;
}
if (!(removeNoise)) {
if (threshold > 0) {
let detectedCut = InductiveMiner.inductiveMiner(log, treeParent, activityKey, true, threshold, freqDfg, skippable);
if (detectedCut != null) {
return detectedCut;
}
}
let detectedFallthrough = null;
if (log != null) {
detectedFallthrough = InductiveMiner.detectFallthroughs(log, freqDfg, treeParent, activityKey, threshold);
}
else {
detectedFallthrough = InductiveMiner.detectFallthroughsDfg(freqDfg, treeParent, threshold);
}
if (detectedFallthrough != null) {
return detectedFallthrough;
}
}
return InductiveMiner.mineFlower(freqDfg, treeParent);
}
static removeNoiseFromDfg(freqDfg, threshold) {
let maxPerActivity = {};
for (let ea in freqDfg.endActivities) {
maxPerActivity[ea] = freqDfg.endActivities[ea];
}
for (let path in freqDfg.pathsFrequency) {
let pf = freqDfg.pathsFrequency[path];
let act1 = path.split(",")[0];
if (!(act1 in maxPerActivity)) {
maxPerActivity[act1] = pf;
}
else {
maxPerActivity[act1] = Math.max(pf, maxPerActivity[act1]);
}
}
for (let path in freqDfg.pathsFrequency) {
let pf = freqDfg.pathsFrequency[path];
let act1 = path.split(",")[0];
if (pf < (1 - threshold)*maxPerActivity[act1]) {
delete freqDfg.pathsFrequency[path];
}
}
return freqDfg;
}
static detectCut(log, freqDfg, treeParent, activityKey, threshold) {
if (freqDfg == null) {
freqDfg = FrequencyDfgDiscovery.apply(log, activityKey);
}
let seqCut = InductiveMinerSequenceCutDetector.detect(freqDfg, activityKey);
let vect = null;
let subdfgs = null;
let skippable = null;
if (seqCut != null) {
//console.log("InductiveMinerSequenceCutDetector");
let seqNode = new ProcessTree(treeParent, ProcessTreeOperator.SEQUENCE, null);
vect = InductiveMinerSequenceCutDetector.projectDfg(freqDfg, seqCut);
subdfgs = vect[0];
skippable = vect[1];
if (log != null) {
let logs = InductiveMinerSequenceCutDetector.project(log, seqCut, activityKey);
for (let sublog of logs) {
let child = InductiveMiner.inductiveMiner(sublog, seqNode, activityKey, false, threshold);
seqNode.children.push(child);
}
}
else {
for (let idx in subdfgs) {
let child = InductiveMiner.inductiveMiner(null, seqNode, activityKey, false, threshold, subdfgs[idx], skippable[idx]);
seqNode.children.push(child);
}
}
return seqNode;
}
let xorCut = InductiveMinerExclusiveCutDetector.detect(freqDfg, activityKey);
if (xorCut != null) {
//console.log("InductiveMinerExclusiveCutDetector");
let xorNode = new ProcessTree(treeParent, ProcessTreeOperator.EXCLUSIVE, null);
vect = InductiveMinerExclusiveCutDetector.projectDfg(freqDfg, xorCut);
subdfgs = vect[0];
skippable = vect[1];
if (log != null) {
let logs = InductiveMinerExclusiveCutDetector.project(log, xorCut, activityKey);
for (let sublog of logs) {
let child = InductiveMiner.inductiveMiner(sublog, xorNode, activityKey, false, threshold);
xorNode.children.push(child);
}
}
else {
for (let idx in subdfgs) {
let child = InductiveMiner.inductiveMiner(null, xorNode, activityKey, false, threshold, subdfgs[idx], skippable[idx]);
xorNode.children.push(child);
}
}
return xorNode;
}
let andCut = InductiveMinerParallelCutDetector.detect(freqDfg, activityKey);
if (andCut != null) {
//console.log("InductiveMinerParallelCutDetector");
let parNode = new ProcessTree(treeParent, ProcessTreeOperator.PARALLEL, null);
vect = InductiveMinerParallelCutDetector.projectDfg(freqDfg, andCut);
subdfgs = vect[0];
skippable = vect[1];
if (log != null) {
let logs = InductiveMinerParallelCutDetector.project(log, andCut, activityKey);
for (let sublog of logs) {
let child = InductiveMiner.inductiveMiner(sublog, parNode, activityKey, false, threshold);
parNode.children.push(child);
}
}
else {
for (let idx in subdfgs) {
let child = InductiveMiner.inductiveMiner(null, parNode, activityKey, false, threshold, subdfgs[idx], skippable[idx]);
parNode.children.push(child);
}
}
return parNode;
}
let loopCut = InductiveMinerLoopCutDetector.detect(freqDfg, activityKey);
if (loopCut != null) {
//console.log("InductiveMinerLoopCutDetector");
let loopNode = new ProcessTree(treeParent, ProcessTreeOperator.LOOP, null);
vect = InductiveMinerLoopCutDetector.projectDfg(freqDfg, loopCut);
subdfgs = vect[0];
skippable = vect[1];
if (log != null) {
let logs = InductiveMinerLoopCutDetector.project(log, loopCut, activityKey);
loopNode.children.push(InductiveMiner.inductiveMiner(logs[0], loopNode, activityKey, false, threshold));
loopNode.children.push(InductiveMiner.inductiveMiner(logs[1], loopNode, activityKey, false, threshold));
}
else {
loopNode.children.push(InductiveMiner.inductiveMiner(null, loopNode, activityKey, false, threshold, subdfgs[0], skippable[0]));
loopNode.children.push(InductiveMiner.inductiveMiner(null, loopNode, activityKey, false, threshold, subdfgs[1], skippable[1]));
}
return loopNode;
}
return null;
}
static detectFallthroughsDfg(freqDfg, treeParent, threshold) {
let activityConcurrentCut = InductiveMinerActivityConcurrentFallthroughDFG.detect(freqDfg, threshold);
if (activityConcurrentCut != null) {
let parNode = new ProcessTree(treeParent, ProcessTreeOperator.PARALLEL, null);
let xorWithSkipsNode = new ProcessTree(treeParent, ProcessTreeOperator.EXCLUSIVE, null);
parNode.children.push(xorWithSkipsNode);
let actNode = new ProcessTree(xorWithSkipsNode, null, activityConcurrentCut[0]);
let skipNode = new ProcessTree(xorWithSkipsNode, null, null);
xorWithSkipsNode.children.push(actNode);
xorWithSkipsNode.children.push(skipNode);
let xorWithSkipsNode2 = new ProcessTree(parNode, ProcessTreeOperator.EXCLUSIVE, null);
let skipNode2 = new ProcessTree(xorWithSkipsNode2, null, null);
xorWithSkipsNode2.children.push(activityConcurrentCut[1]);
xorWithSkipsNode2.children.push(skipNode2);
activityConcurrentCut[1].parentNode = xorWithSkipsNode2;
parNode.children.push(xorWithSkipsNode2);
parNode.properties["concurrentActivity"] = activityConcurrentCut[0];
return parNode;
}
}
static detectFallthroughs(log, freqDfg, treeParent, activityKey, threshold) {
let activityOncePerTraceCandidate = InductiveMinerActivityOncePerTraceFallthrough.detect(log, freqDfg, activityKey);
if (activityOncePerTraceCandidate != null) {
//console.log("InductiveMinerActivityOncePerTraceFallthrough");
let sublog = InductiveMinerActivityOncePerTraceFallthrough.project(log, activityOncePerTraceCandidate, activityKey);
let parNode = new ProcessTree(treeParent, ProcessTreeOperator.PARALLEL, null);
let actNode = new ProcessTree(parNode, null, activityOncePerTraceCandidate);
parNode.children.push(actNode);
parNode.children.push(InductiveMiner.inductiveMiner(sublog, parNode, activityKey, false, threshold));
return parNode;
}
let activityConcurrentCut = InductiveMinerActivityConcurrentFallthrough.detect(log, freqDfg, activityKey, threshold);
if (activityConcurrentCut != null) {
//console.log("InductiveMinerActivityConcurrentFallthrough");
let parNode = new ProcessTree(treeParent, ProcessTreeOperator.PARALLEL, null);
let filteredLog = LogGeneralFiltering.filterEventsHavingEventAttributeValues(log, [activityConcurrentCut[0]], true, true, activityKey);
parNode.children.push(InductiveMiner.inductiveMiner(filteredLog, parNode, activityKey, false, threshold));
activityConcurrentCut[1].parentNode = parNode;
parNode.children.push(activityConcurrentCut[1]);
parNode.properties["concurrentActivity"] = activityConcurrentCut[0];
return parNode;
}
let strictTauLoop = InductiveMinerStrictTauLoopFallthrough.detect(log, freqDfg, activityKey);
if (strictTauLoop != null) {
//console.log("InductiveMinerStrictTauLoopFallthrough");
let loop = new ProcessTree(treeParent, ProcessTreeOperator.LOOP, null);
let redo = new ProcessTree(loop, null, null);
loop.children.push(InductiveMiner.inductiveMiner(strictTauLoop, loop, activityKey, false, threshold));
loop.children.push(redo);
return loop;
}
let tauLoop = InductiveMinerTauLoopFallthrough.detect(log, freqDfg, activityKey);
if (tauLoop != null) {
//console.log("InductiveMinerTauLoopFallthrough");
let loop = new ProcessTree(treeParent, ProcessTreeOperator.LOOP, null);
let redo = new ProcessTree(loop, null, null);
loop.children.push(InductiveMiner.inductiveMiner(tauLoop, loop, activityKey, false, threshold));
loop.children.push(redo);
return loop;
}
return null;
}
static mineFlower(freqDfg, treeParent) {
let loop = new ProcessTree(treeParent, ProcessTreeOperator.LOOP, null);
let xor = new ProcessTree(loop, ProcessTreeOperator.EXCLUSIVE, null);
let redo = new ProcessTree(loop, null, null);
loop.children.push(xor);
loop.children.push(redo);
for (let act in freqDfg.activities) {
let actNode = new ProcessTree(xor, null, act);
xor.children.push(actNode);
}
return loop;
}
static baseCase(freqDfg, treeParent) {
if (Object.keys(freqDfg.activities).length == 0) {
return new ProcessTree(treeParent, null, null);
}
else if (Object.keys(freqDfg.activities).length == 1) {
let activities = Object.keys(freqDfg.activities);
return new ProcessTree(treeParent, null, activities[0]);
}
else {
let xor = new ProcessTree(treeParent, ProcessTreeOperator.EXCLUSIVE, null);
for (let act in freqDfg.activities) {
let actNode = new ProcessTree(xor, null, act);
xor.children.push(actNode);
}
return xor;
}
}
static countEmptyTraces(eventLog) {
let ret = 0;
for (let trace of eventLog.traces) {
if (trace.events.length == 0) {
ret++;
}
}
return ret;
}
static filterNonEmptyTraces(eventLog) {
let filteredLog = new EventLog();
for (let trace of eventLog.traces) {
if (trace.events.length > 0) {
filteredLog.traces.push(trace);
}
}
return filteredLog;
}
}
class InductiveMinerSequenceCutDetector {
// Basic Steps:
// 1. create a group per activity
// 2. merge pairwise reachable nodes (based on transitive relations)
// 3. merge pairwise unreachable nodes (based on transitive relations)
// 4. sort the groups based on their reachability
static detect(freqDfg, activityKey) {
let actReach = InductiveMinerGeneralUtilities.activityReachability(freqDfg);
let groups = [];
for (let act in actReach) {
groups.push([act]);
}
let groupsSize = null;
while (groupsSize != groups.length) {
groupsSize = groups.length;
groups = InductiveMinerSequenceCutDetector.mergeGroups(groups, actReach);
}
groups = InductiveMinerSequenceCutDetector.sortBasedOnReachability(groups, actReach);
if (groups.length > 1) {
return groups;
}
return null;
}
static mergeGroups(groups, actReach) {
let i = 0;
while (i < groups.length) {
let j = i + 1;
while (j < groups.length) {
if (InductiveMinerSequenceCutDetector.checkMergeCondition(groups[i], groups[j], actReach)) {
groups[i] = [...groups[i], ...groups[j]];
groups.splice(j, 1);
continue;
}
j++;
}
i++;
}
return groups;
}
static checkMergeCondition(g1, g2, actReach) {
for (let a1 of g1) {
for (let a2 of g2) {
if ((a2 in actReach[a1] && a1 in actReach[a2]) || (!(a2 in actReach[a1]) && !(a1 in actReach[a2]))) {
return true;
}
}
}
return false;
}
static sortBasedOnReachability(groups, actReach) {
let cont = true;
while (cont) {
cont = false;
let i = 0;
while (i < groups.length) {
let j = i + 1;
while (j < groups.length) {
for (let act1 of groups[i]) {
for (let act2 of groups[j]) {
if (act1 in actReach[act2]) {
let temp = groups[i];
groups[i] = groups[j];
groups[j] = temp;
cont = true;
break;
}
}
if (cont) {
break;
}
}
if (cont) {
break;
}
j++;
}
if (cont) {
break;
}
i++;
}
}
return groups;
}
static projectDfg(dfg, groups) {
let startActivities = [];
let endActivities = [];
let activities = [];
let dfgs = [];
let skippable = [];
for (let g of groups) {
skippable.push(false);
}
let activitiesIdx = {};
for (let gind in groups) {
let g = groups[gind]
for (let act of g) {
activitiesIdx[act] = parseInt(gind);
}
}
let i = 0;
while (i < groups.length) {
let toSuccArcs = {};
let fromPrevArcs = {};
if (i < groups.length - 1) {
for (let arc0 in dfg.pathsFrequency) {
let arc = arc0.split(",");
if (groups[i].includes(arc[0]) && groups[i+1].includes(arc[1])) {
if (!(arc[0] in toSuccArcs)) {
toSuccArcs[arc[0]] = 0;
}
toSuccArcs[arc[0]] += dfg.pathsFrequency[arc0];
}
}
}
if (i > 0) {
for (let arc0 in dfg.pathsFrequency) {
let arc = arc0.split(",");
if (groups[i-1].includes(arc[0]) && groups[i].includes(arc[1])) {
if (!(arc[1] in fromPrevArcs)) {
fromPrevArcs[arc[1]] = 0;
}
fromPrevArcs[arc[1]] += dfg.pathsFrequency[arc0];
}
}
}
if (i == 0) {
startActivities.push({});
for (let act in dfg.startActivities) {
if (groups[i].includes(act)) {
startActivities[i][act] = dfg.startActivities[act];
}
else {
let j = i;
while (j < activitiesIdx[act]) {
skippable[j] = true;
j++;
}
}
}
}
else {
startActivities.push(fromPrevArcs);
}
if (i == groups.length - 1) {
endActivities.push({});
for (let act in dfg.endActivities) {
if (groups[i].includes(act)) {
endActivities[i][act] = dfg.endActivities[act];
}
else {
let j = activitiesIdx[act] + 1;
while (j <= i) {
skippable[j] = true;
j++;
}
}
}
}
else {
endActivities.push(toSuccArcs);
}
activities.push({});
for (let act of groups[i]) {
activities[i][act] = dfg.activities[act];
}
dfgs.push({});
for (let arc0 in dfg.pathsFrequency) {
let arc = arc0.split(",");
if (groups[i].includes(arc[0]) && groups[i].includes(arc[1])) {
dfgs[i][arc0] = dfg.pathsFrequency[arc0];
}
}
i++;
}
i = 0;
while (i < dfgs.length) {
dfgs[i] = new FrequencyDfg(activities[i], startActivities[i], endActivities[i], dfgs[i]);
i++;
}
for (let arc0 in dfg.pathsFrequency) {
let arc = arc0.split(",");
let z = activitiesIdx[arc[1]];
let j = activitiesIdx[arc[0]] + 1;
while (j < z) {
skippable[j] = false;
j++;
}
}
return [dfgs, skippable];
}
static project(log, groups, activityKey) {
let logs = [];
for (let g of groups) {
logs.push(new EventLog());
}
for (let trace of log.traces) {
let i = 0;
let splitPoint = 0;
let actUnion = [];
while (i < groups.length) {
let newSplitPoint = InductiveMinerSequenceCutDetector.findSplitPoint(trace, groups[i], splitPoint, actUnion, activityKey);
let tracei = new Trace();
let j = splitPoint;
while (j < newSplitPoint) {
if (groups[i].includes(trace.events[j].attributes[activityKey].value)) {
tracei.events.push(trace.events[j]);
}
j++;
}
logs[i].traces.push(tracei);
splitPoint = newSplitPoint;
for (let act of groups[i]) {
actUnion.push(act);
}
i++;
}
}
return logs;
}
static findSplitPoint(trace, group, start, ignore, activityKey) {
let leastCost = 0
let positionWithLeastCost = start;
let cost = 0;
let i = start;
while (i < trace.events.length) {
if (group.includes(trace.events[i].attributes[activityKey].value)) {
cost = cost - 1
}
else if (!(ignore.includes(trace.events[i].attributes[activityKey].value))) {
cost = cost + 1
}
if (cost < leastCost) {
leastCost = cost;
positionWithLeastCost = i + 1;
}
i++;
}
return positionWithLeastCost;
}
}
class InductiveMinerLoopCutDetector {
// 1. merge all start and end activities in one group ('do' group)
// 2. remove start/end activities from the dfg
// 3. detect connected components in (undirected representative) of the reduced graph
// 4. check if each component meets the start/end criteria of the loop cut definition (merge with the 'do' group if not)
// 5. return the cut if at least two groups remain
static detect(freqDfg0, activityKey) {
let freqDfg = Object();
freqDfg.pathsFrequency = {};
for (let path in freqDfg0.pathsFrequency) {
let act1 = path.split(",")[0];
let act2 = path.split(",")[1];
if (!(act1 in freqDfg0.startActivities || act2 in freqDfg0.startActivities || act1 in freqDfg0.endActivities || act2 in freqDfg0.endActivities)) {
freqDfg.pathsFrequency[path] = freqDfg0.pathsFrequency[path];
}
}
let doPart = [];
let redoPart = [];
let remainingActivities = {};
for (let act in freqDfg0.activities) {
if (act in freqDfg0.startActivities || act in freqDfg0.endActivities) {
doPart.push(act);
}
else {
remainingActivities[act] = freqDfg0.activities[act];
}
}
freqDfg.activities = remainingActivities;
let actReach = InductiveMinerGeneralUtilities.activityReachability(freqDfg0);
let connComp = InductiveMinerGeneralUtilities.getConnectedComponents(freqDfg);
for (let conn of connComp) {
let isRedo = true;
for (let act in freqDfg0.startActivities) {
for (let act2 of conn) {
if (!(act2 in actReach[act])) {
isRedo = false;
break;
}
}
}
if (isRedo) {
for (let act in freqDfg0.endActivities) {
for (let act2 of conn) {
if (!(act2 in actReach[act])) {
isRedo = false;
break;
}
}
}
}
if (isRedo) {
for (let act of conn) {
for (let sa in freqDfg0.startActivities) {
if (!(sa in freqDfg0.endActivities)) {
if (!([act, sa] in freqDfg0.pathsFrequency)) {
isRedo = false;
break;
}
}
}
}
}
if (isRedo) {
for (let act of conn) {
for (let ea in freqDfg0.endActivities) {
if (!(ea in freqDfg0.startActivities)) {
if (!([ea, act] in freqDfg0.pathsFrequency)) {
isRedo = false;
break;
}
}
}
}
}
for (let act of conn) {
if (isRedo) {
redoPart.push(act);
}
else {
doPart.push(act);
}
}
}
if (redoPart.length > 0) {
return [doPart, redoPart];
}
return null;
}
static project(log, groups, activityKey) {
let sublogs = [new EventLog(), new EventLog()];
for (let trace of log.traces) {
let i = 0;
let j = 0;
let subtraceDo = new Trace();
let subtraceRedo = new Trace();
while (i < trace.events.length) {
let thisAct = trace.events[i].attributes[activityKey].value;
if (groups[0].includes(thisAct)) {
if (j == 1) {
sublogs[1].traces.push(subtraceRedo);
subtraceRedo = new Trace();
}
j = 0;
}
else if (groups[1].includes(thisAct)) {
if (j == 0) {
sublogs[0].traces.push(subtraceDo);
subtraceDo = new Trace();
}
j = 1;
}
else {
i++;
continue;
}
if (j == 0) {
subtraceDo.events.push(trace.events[i]);
}
else {
subtraceRedo.events.push(trace.events[i]);
}
i++;
}
if (j == 0) {
sublogs[0].traces.push(subtraceDo);
}
}
return sublogs;
}
static projectDfg(frequencyDfg, groups) {
let dfgs = [];
let skippable = [false, false];
for (let gind in groups) {
let g = groups[gind];
let activities = {};
let startActivities = {};
let endActivities = {};
let pathsFrequency = {};
for (let act in frequencyDfg.activities) {
if (g.includes(act)) {
activities[act] = frequencyDfg.activities[act];
}
}
for (let arc0 in frequencyDfg.pathsFrequency) {
let arc = arc0.split(",");
if (g.includes(arc[0]) && g.includes(arc[1])) {
pathsFrequency[arc0] = frequencyDfg.pathsFrequency[arc0];
}
if (arc[1] in frequencyDfg.startActivities && arc[0] in frequencyDfg.endActivities) {
skippable[1] = true;
}
}
if (gind == 0) {
for (let act in frequencyDfg.startActivities) {
if (g.includes(act)) {
startActivities[act] = frequencyDfg.startActivities[act];
}
else {
skippable[0] = true;
}
}
for (let act in frequencyDfg.endActivities) {
if (g.includes(act)) {
endActivities[act] = frequencyDfg.endActivities[act];
}
else {
skippable[0] = true;
}
}
}
else if (gind == 1) {
for (let act of g) {
startActivities[act] = 1;
endActivities[act] = 1;
}
}
dfgs.push(new FrequencyDfg(activities, startActivities, endActivities, pathsFrequency));
}
return [dfgs, skippable];
}
}
class InductiveMinerParallelCutDetector {
static detect(freqDfg, activityKey) {
let ret = [];
for (let act in freqDfg.activities) {
ret.push([act]);
}
let cont = true;
while (cont) {
cont = false;
let i = 0;
while (i < ret.length) {
let j = i + 1;
while (j < ret.length) {
for (let act1 of ret[i]) {
if (ret[j] != null) {
for (let act2 of ret[j]) {
if ((!([act1, act2] in freqDfg.pathsFrequency)) || (!([act2, act1] in freqDfg.pathsFrequency))) {
ret[i] = [...ret[i], ...ret[j]];
ret.splice(j, 1);
cont = true;
break;
}
}
if (cont) {
break;
}
}
}
if (cont) {
break;
}
j++;
}
if (cont) {
break;
}
i++;
}
}
ret.sort(function(a, b) {
if (a.length < b.length) {
return -1;
}
else if (a.length > b.length) {
return 1;
}
return 0;
});
if (ret.length > 1) {
let i = 0;
while (i < ret.length) {
let containsSa = false;
let containsEa = false;
for (let sa in freqDfg.startActivities) {
if (ret[i].includes(sa)) {
containsSa = true;
break;
}
}
for (let ea in freqDfg.endActivities) {
if (ret[i].includes(ea)) {
containsEa = true;
break;
}
}
if (!(containsSa && containsEa)) {
let targetIdx = i-1;
if (targetIdx < 0) {
targetIdx = i+1;
}
if (targetIdx < ret.length) {
ret[targetIdx] = [...ret[targetIdx], ...ret[i]];
}
ret.splice(i, 1);
continue;
}
i++;
}
if (ret.length > 1) {
return ret;
}
}
return null;
}
static project(log, groups, activityKey) {
let ret = [];
for (let g of groups) {
ret.push(LogGeneralFiltering.filterEventsHavingEventAttributeValues(log, g, true, true, activityKey));
}
return ret;
}
static projectDfg(frequencyDfg, groups) {
let dfgs = [];
let skippable = [];
for (let gind in groups) {
let g = groups[gind];
let startActivities = {};
let endActivities = {};
let activities = {};
let pathsFrequency = {};
for (let act in frequencyDfg.startActivities) {
if (g.includes(act)) {
startActivities[act] = frequencyDfg.startActivities[act];
}
}
for (let act in frequencyDfg.endActivities) {
if (g.includes(act)) {
endActivities[act] = frequencyDfg.endActivities[act];
}
}
for (let act in frequencyDfg.activities) {
if (g.includes(act)) {
activities[act] = frequencyDfg.activities[act];
}
}
for (let arc0 in frequencyDfg.pathsFrequency) {
let arc = arc0.split(",");
if (g.includes(arc[0]) && g.includes(arc[1])) {
pathsFrequency[arc0] = frequencyDfg.pathsFrequency[arc0];
}
}
dfgs.push(new FrequencyDfg(activities, startActivities, endActivities, pathsFrequency));
skippable.push(false);
}
return [dfgs, skippable];
}
}
class InductiveMinerExclusiveCutDetector {
static detect(freqDfg, activityKey) {
let connComp = InductiveMinerGeneralUtilities.getConnectedComponents(freqDfg);
if (connComp.length > 1) {
return connComp;
}
return null;
}
static projectDfg(frequencyDfg, groups) {
let dfgs = [];
let skippable = [];
for (let gind in groups) {
let g = groups[gind];
let startActivities = {};
let endActivities = {};
let activities = {};
let pathsFrequency = {};
for (let act in frequencyDfg.startActivities) {
if (g.includes(act)) {
startActivities[act] = frequencyDfg.startActivities[act];
}
}
for (let act in frequencyDfg.endActivities) {
if (g.includes(act)) {
endActivities[act] = frequencyDfg.endActivities[act];
}
}
for (let act in frequencyDfg.activities) {
if (g.includes(act)) {
activities[act] = frequencyDfg.activities[act];
}
}
for (let arc0 in frequencyDfg.pathsFrequency) {
let arc = arc0.split(",");
if (g.includes(arc[0]) && g.includes(arc[1])) {
pathsFrequency[arc0] = frequencyDfg.pathsFrequency[arc0];
}
}
dfgs.push(new FrequencyDfg(activities, startActivities, endActivities, pathsFrequency));
skippable.push(false);
}
return [dfgs, skippable];
}
static project(log, groups, activityKey) {
let ret = [];
for (let g of groups) {
ret.push(new EventLog());
}
for (let trace of log.traces) {
let gc = {};
let i = 0;
while (i < groups.length) {
gc[i] = 0;
i++;
}
let activ = [];
for (let eve of trace.events) {
activ.push(eve.attributes[activityKey].value);
}
let maxv = -1;
let maxi = 0;
i = 0;
while (i < groups.length) {
for (let act of groups[i]) {
if (activ.includes(act)) {
gc[i]++;
if (gc[i] > maxv) {
maxv = gc[i];
maxi = i;
}
}
}
i++;
}
let projectedTrace = new Trace();
for (let eve of trace.events) {
let act = eve.attributes[activityKey].value;
if (groups[maxi].includes(act)) {
projectedTrace.events.push(eve);
}
}
ret[maxi].traces.push(projectedTrace);
}
return ret;
}
}
class InductiveMinerActivityOncePerTraceFallthrough {
static detect(log, freqDfg, activityKey) {
if (Object.keys(freqDfg.activities).length > 1) {
let inte = null;
for (let trace of log.traces) {
let activities = {};
for (let eve of trace.events) {
let act = eve.attributes[activityKey].value;
if (!(act in activities)) {
activities[act] = 1;
}
else {
activities[act] += 1;
}
}
if (inte != null) {
for (let act in activities) {
if (!(act in inte) || activities[act] > 1) {
delete activities[act];
}
}
}
inte = activities;
}
if (inte != null) {
inte = Object.keys(inte);
if (inte.length > 0) {
return inte[0];
}
}
}
return null;
}
static project(log, act, activityKey) {
return LogGeneralFiltering.filterEventsHavingEventAttributeValues(log, [act], true, false, activityKey);
}
}
class InductiveMinerActivityConcurrentFallthrough {
static detect(log, freqDfg, activityKey, threshold) {
if (Object.keys(freqDfg.activities).length > 1) {
for (let act in freqDfg.activities) {
let sublog = LogGeneralFiltering.filterEventsHavingEventAttributeValues(log, [act], true, false, activityKey);
let detectedCut = InductiveMiner.detectCut(sublog, null, null, activityKey, threshold);
if (detectedCut != null) {
return [act, detectedCut];
}
}
}
return null;
}
static project(log, act, activityKey) {
return LogGeneralFiltering.filterEventsHavingEventAttributeValues(log, [act], true, false, activityKey);
}
}
class InductiveMinerActivityConcurrentFallthroughDFG {
static removeActFromDFG(freqDfg, activity) {
let activities = {};
let startActivities = {};
let endActivities = {};
let pathsFrequency = {};
for (let act in freqDfg.activities) {
if (act != activity) {
activities[act] = freqDfg.activities[act];
}
}
for (let act in freqDfg.startActivities) {
if (act != activity) {
startActivities[act] = freqDfg.startActivities[act];
}
}
for (let act in freqDfg.endActivities) {
if (act != activity) {
endActivities[act] = freqDfg.endActivities[act];
}
}
for (let path0 in freqDfg.pathsFrequency) {
let path = path0.split(",");
if (path[0] != activity && path[1] != activity) {
pathsFrequency[path0] = freqDfg.pathsFrequency[path0];
}
}
return new FrequencyDfg(activities, startActivities, endActivities, pathsFrequency);
}
static detect(freqDfg, threshold) {
for (let act in freqDfg.activities) {
let subdfg = InductiveMinerActivityConcurrentFallthroughDFG.removeActFromDFG(freqDfg, act);
let detectedCut = InductiveMiner.detectCut(null, subdfg, null, null, threshold);
if (detectedCut != null) {
return [act, detectedCut];
}
}
return null;
}
static projectDfg(frequencyDfg, act) {
return InductiveMinerActivityConcurrentFallthroughDFG.removeActFromDFG(freqDfg, act);
}
}
class InductiveMinerStrictTauLoopFallthrough {
static detect(log, freqDfg, activityKey) {
let proj = new EventLog();
for (let trace of log.traces) {
let x = 0;
let i = 1;
while (i < trace.events.length) {
let act_curr = trace.events[i].attributes[activityKey].value;
let act_prev = trace.events[i-1].attributes[activityKey].value;
if (act_curr in freqDfg.startActivities && act_prev in freqDfg.endActivities) {
let subtrace = new Trace();
let j = x;
while (j < i) {
subtrace.events.push(trace.events[j]);
j++;
}
proj.traces.push(subtrace);
x = i;
}
i++;
}
let j = x;
let subtrace = new Trace();
while (j < trace.events.length) {
subtrace.events.push(trace.events[j]);
j++;
}
proj.traces.push(subtrace);
}
if (proj.traces.length > log.traces.length) {
return proj;
}
return null;
}
}
class InductiveMinerTauLoopFallthrough {
static detect(log, freqDfg, activityKey) {
let proj = new EventLog();
for (let trace of log.traces) {
let x = 0;
let i = 1;
while (i < trace.events.length) {
let act_curr = trace.events[i].attributes[activityKey].value;
if (act_curr in freqDfg.startActivities) {
let subtrace = new Trace();
let j = x;
while (j < i) {
subtrace.events.push(trace.events[j]);
j++;
}
proj.traces.push(subtrace);
x = i;
}
i++;
}
let j = x;
let subtrace = new Trace();
while (j < trace.events.length) {
subtrace.events.push(trace.events[j]);
j++;
}
proj.traces.push(subtrace);
}
if (proj.traces.length > log.traces.length) {
return proj;
}
return null;
}
}
class InductiveMinerGeneralUtilities {
static activityReachability(freqDfg) {
let ret = {};
for (let act in freqDfg.activities) {
ret[act] = {};
}
for (let rel in freqDfg.pathsFrequency) {
let act1 = rel.split(",")[0];
let act2 = rel.split(",")[1];
ret[act1][act2] = 0;
}
let cont = true;
while (cont) {
cont = false;
for (let act in ret) {
for (let act2 in ret[act]) {
for (let act3 in ret[act2]) {
if (!(act3 in ret[act])) {
ret[act][act3] = 0;
cont = true;
}
}
}
}
}
return ret;
}
static getConnectedComponents(freqDfg) {
let ret = [];
for (let act in freqDfg.activities) {
ret.push([act]);
}
let cont = true;
while (cont) {
cont = false;
let i = 0;
while (i < ret.length) {
let j = i + 1;
while (j < ret.length) {
for (let act1 of ret[i]) {
if (ret[j] != null) {
for (let act2 of ret[j]) {
if ([act1, act2] in freqDfg.pathsFrequency || [act2, act1] in freqDfg.pathsFrequency) {
ret[i] = [...ret[i], ...ret[j]];
ret.splice(j, 1);
cont = true;
break;
}
}
if (cont) {
break;
}
}
}
if (cont) {
break;
}
j++;
}
if (cont) {
break;
}
i++;
}
}
return ret;
}
}
try {
module.exports = {InductiveMiner: InductiveMiner, InductiveMinerSequenceCutDetector: InductiveMinerSequenceCutDetector};
global.InductiveMiner = InductiveMiner;
global.InductiveMinerSequenceCutDetector = InductiveMinerSequenceCutDetector;
}
catch (err) {
// not in Node
//console.log(err);
}
Pm4JS.registerAlgorithm("InductiveMiner", "applyPlugin", ["EventLog"], "ProcessTree", "Mine a Process Tree using the Inductive Miner", "Alessandro Berti");
Pm4JS.registerAlgorithm("InductiveMiner", "applyPluginDFG", ["FrequencyDfg"], "ProcessTree", "Mine a Process Tree using the Inductive Miner Directly-Follows", "Alessandro Berti");
Pm4JS.registerAlgorithm("InductiveMiner", "applyPluginDFG", ["PerformanceDfg"], "ProcessTree", "Mine a Process Tree using the Inductive Miner Directly-Follows", "Alessandro Berti");