/// <summary> /// Goes through the steps of the (improved) HeuristicMiner Algorithm: /// Find starting-events => build adjacency-matrix => convert into petrinet /// </summary> /// <returns>A PetriNet as a ProcessModel</returns> /// <exception cref="ArgumentNullException">If the field-parameter is null.</exception> /// <author>Jannik Arndt</author> public ProcessModel Mine() { if (_field == null) throw new Exception("The field parameter was null"); // Get parameters from MinerSettings var threshold = MinerSettings.GetAsDouble("AdjacencyThresholdSlider") / 100; var maxDepth = MinerSettings.GetAsInt("MaximumRecursionDepthSlider"); if (threshold < 0 || threshold > 1) throw new Exception("Threshold must be between 0 and 1."); if (maxDepth < 1) throw new Exception("Maximum recursion depth must be more greater than 1."); // Statistics var processingTimeStart = Process.GetCurrentProcess().TotalProcessorTime; // 1. Create Adjacency Matrix var adjacencyMatrix = CreateAdjacencyMatrix(_field); // 2. Calculate the indexes of the first events var startindexes = GetColumnsWithOnlyNegativeEntries(adjacencyMatrix); // for every sub-processmodel foreach (var startindex in startindexes) { // 3. Create the dependency-Graph var rootNode = CreateDependencyGraph(_field.EventLog.ListOfUniqueEvents, adjacencyMatrix, threshold, 0.0, startindex, maxDepth); // 4. Turn the dependency-graph into a petri-net _listOfSubPetriNets.Add(CreatePetriNetFromDependencyGraph(rootNode, _field.EventLog, _field.Infotext)); } // 5. Connect all Petrinets by one XOR-Split if (_listOfSubPetriNets.Count > 1) { // 5a. Connect all subnets to one start var startingPlace = _finalPetriNet.AddPlace("Start"); foreach (var petriNet in _listOfSubPetriNets) { var xorTransition = _finalPetriNet.AddTransition(incomingPlace: startingPlace); _finalPetriNet.MergeWithPetriNet(petriNet, xorTransition); } // 5b. Add following transitions to all subnet-endings foreach (var openEndPlace in _finalPetriNet.GetSinks()) if (openEndPlace.GetType() == typeof(Place)) _finalPetriNet.AddTransition(incomingPlace: (Place)openEndPlace); } else _finalPetriNet = _listOfSubPetriNets[0]; // 6. Connect all open endings _finalPetriNet = FixEnding(_finalPetriNet); // 7. Delete empty transitions _finalPetriNet = PetriNetOperation.CleanUpPetriNet(_finalPetriNet); // 8. Save information about the mining-process var processingTimeEnd = Process.GetCurrentProcess().TotalProcessorTime; var processingTime = Math.Abs((processingTimeEnd - processingTimeStart).TotalMilliseconds) < 1 ? "< 15 ms" : (processingTimeEnd - processingTimeStart).TotalMilliseconds + " ms"; _field.Information.Add("Processing Time", processingTime); _field.Information.Add("Number of Events", _finalPetriNet.Transitions.Count(s => s.IsLoop == false).ToString(CultureInfo.InvariantCulture)); _field.Information.Add("Number of Transitions", _finalPetriNet.Transitions.Count.ToString(CultureInfo.InvariantCulture)); _field.Information.Add("Number of Places", _finalPetriNet.Places.Count.ToString(CultureInfo.InvariantCulture)); _field.Information.Add("Loops in the net", _finalPetriNet.CountLoops().ToString(CultureInfo.InvariantCulture)); _field.Information.Add("Events used", _finalPetriNet.CountTransitionsWithoutANDs() - _finalPetriNet.CountLoops() + " of " + _field.EventLog.ListOfUniqueEvents.Count); _field.Information.Add("Parallel Models", startindexes.Count.ToString(CultureInfo.InvariantCulture)); if (_listOfNodes.Count > 0) { _field.Information.Add("Minimal Adjacency", _listOfNodes.Min().ToString(CultureInfo.InvariantCulture)); _field.Information.Add("Average Adjacency", Math.Round(_listOfNodes.Average(), 3).ToString(CultureInfo.InvariantCulture)); if (Math.Abs(_listOfNodes.Min() - _listOfNodes.Average()) > 0.0001) _field.Information.Add("Standard Deviation", CalculateStandardDeviation(_listOfNodes).ToString(CultureInfo.InvariantCulture)); } return _finalPetriNet; }