Source code for pm4py.algo.evaluation.replay_fitness.variants.alignment_based

'''
    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
'''
from pm4py.algo.conformance.alignments.petri_net import algorithm as alignments
from pm4py.algo.conformance.alignments.decomposed import (
    algorithm as decomp_alignments,
)
from pm4py.util import exec_utils
from enum import Enum
from pm4py.util import constants
from typing import Optional, Dict, Any, Union
from pm4py.objects.log.obj import EventLog, Trace
from pm4py.objects.petri_net.obj import PetriNet, Marking
from pm4py.util import typing


[docs] class Parameters(Enum): ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY ATTRIBUTE_KEY = constants.PARAMETER_CONSTANT_ATTRIBUTE_KEY TOKEN_REPLAY_VARIANT = "token_replay_variant" CLEANING_TOKEN_FLOOD = "cleaning_token_flood" MULTIPROCESSING = "multiprocessing"
[docs] def evaluate( aligned_traces: typing.ListAlignments, parameters: Optional[Dict[Union[str, Parameters], Any]] = None, ) -> Dict[str, float]: """ Transforms the alignment result to a simple dictionary including the percentage of fit traces and the average fitness Parameters ---------- aligned_traces Alignments calculated for the traces in the log parameters Possible parameters of the evaluation Returns ---------- dictionary Containing two keys (percFitTraces and averageFitness) """ if parameters is None: parameters = {} str(parameters) no_traces = len([x for x in aligned_traces if x is not None]) no_fit_traces = 0 sum_fitness = 0.0 sum_bwc = 0.0 sum_cost = 0.0 for tr in aligned_traces: if tr is not None: if tr["fitness"] == 1.0: no_fit_traces = no_fit_traces + 1 sum_fitness += tr["fitness"] if "bwc" in tr and "cost" in tr: sum_bwc += tr["bwc"] sum_cost += tr["cost"] perc_fit_traces = 0.0 average_fitness = 0.0 log_fitness = 0.0 if no_traces > 0: perc_fit_traces = (100.0 * float(no_fit_traces)) / (float(no_traces)) average_fitness = float(sum_fitness) / float(no_traces) log_fitness = float(sum_cost) / float(sum_bwc) if sum_bwc > 0 else 0 log_fitness = 1.0 - log_fitness return { "percFitTraces": perc_fit_traces, "averageFitness": average_fitness, "percentage_of_fitting_traces": perc_fit_traces, "average_trace_fitness": average_fitness, "log_fitness": log_fitness, }
[docs] def apply( log: EventLog, petri_net: PetriNet, initial_marking: Marking, final_marking: Marking, align_variant=alignments.DEFAULT_VARIANT, parameters: Optional[Dict[Union[str, Parameters], Any]] = None, ) -> Dict[str, float]: """ Evaluate fitness based on alignments Parameters ---------------- log Event log petri_net Petri net initial_marking Initial marking final_marking Final marking align_variant Variants of the alignments to apply parameters Parameters of the algorithm Returns --------------- dictionary Containing two keys (percFitTraces and averageFitness) """ if parameters is None: parameters = {} multiprocessing = exec_utils.get_param_value( Parameters.MULTIPROCESSING, parameters, constants.ENABLE_MULTIPROCESSING_DEFAULT, ) if align_variant == decomp_alignments.Variants.RECOMPOS_MAXIMAL.value: alignment_result = decomp_alignments.apply( log, petri_net, initial_marking, final_marking, variant=align_variant, parameters=parameters, ) else: if multiprocessing: alignment_result = alignments.apply_multiprocessing( log, petri_net, initial_marking, final_marking, variant=align_variant, parameters=parameters, ) else: alignment_result = alignments.apply( log, petri_net, initial_marking, final_marking, variant=align_variant, parameters=parameters, ) return evaluate(alignment_result)
[docs] def apply_trace( trace: Trace, petri_net: PetriNet, initial_marking: Marking, final_marking: Marking, best_worst: Any, activity_key: str, ) -> typing.AlignmentResult: """ Performs the basic alignment search, given a trace, a net and the costs of the \"best of the worst\". The costs of the best of the worst allows us to deduce the fitness of the trace. We compute the fitness by means of 1 - alignment costs / best of worst costs (i.e. costs of 0 => fitness 1) Parameters ---------- trace: :class:`list` input trace, assumed to be a list of events (i.e. the code will use the activity key to get the attributes) petri_net: :class:`pm4py.objects.petri.net.PetriNet` the Petri net to use in the alignment initial_marking: :class:`pm4py.objects.petri.net.Marking` initial marking in the Petri net final_marking: :class:`pm4py.objects.petri.net.Marking` final marking in the Petri net best_worst: cost of the best worst alignment of a trace (empty trace aligned to the model) activity_key: :class:`str` (optional) key to use to identify the activity described by the events Returns ------- dictionary: `dict` with keys **alignment**, **cost**, **visited_states**, **queued_states** and **traversed_arcs** """ alignment = alignments.apply_trace( trace, petri_net, initial_marking, final_marking, {Parameters.ACTIVITY_KEY: activity_key}, ) fixed_costs = alignment["cost"] // alignments.utils.STD_MODEL_LOG_MOVE_COST if best_worst > 0: fitness = 1 - (fixed_costs / best_worst) else: fitness = 1 return { "trace": trace, "alignment": alignment["alignment"], "cost": fixed_costs, "fitness": fitness, "visited_states": alignment["visited_states"], "queued_states": alignment["queued_states"], "traversed_arcs": alignment["traversed_arcs"], }