Log Filtering with Linear Temporal Properties

The filtering of a log according to Linear Temporal Logic properties is implemented with an LTLf checker based on automata. First of all we create an D4PyEventLog object that wraps the log.

[1]:
import os

from Declare4Py.D4PyEventLog import D4PyEventLog

log_path = os.path.join("../../../", "tests", "test_logs","Sepsis Cases.xes.gz")
event_log = D4PyEventLog()
event_log.parse_xes_log(log_path)
/usr/lib/python3/dist-packages/pyparsing.py:108: DeprecationWarning: module 'sre_constants' is deprecated
  import sre_constants
/usr/local/lib/python3.11/dist-packages/lark/utils.py:163: DeprecationWarning: module 'sre_parse' is deprecated
  import sre_parse
/usr/local/lib/python3.11/dist-packages/pm4py/utils.py:486: UserWarning: the EventLog class has been deprecated and will be removed in a future release.
  warnings.warn("the EventLog class has been deprecated and will be removed in a future release.")

The classes LTLTemplate and LTLModel are responsible for the LTLf template definitions and their instantiations into LTLf models given some activities and/or payload names:

[2]:
from Declare4Py.ProcessModels.LTLModel import LTLTemplate
from Declare4Py.ProcessModels.LTLModel import LTLModel

template : LTLTemplate = LTLTemplate('is_first_state_a')
model_1 : LTLModel= template.fill_template(['ER Registration'], attr_type=['concept:name'])

template: LTLTemplate = LTLTemplate('eventually_a')
model_2 : LTLModel = template.fill_template(['Leucocytes'], attr_type=['concept:name'])

The class LTLAnalyzer is therefore needed to analyze the log and filtering its traces according to multiple input LTLf models.

[3]:
from Declare4Py.ProcessMiningTasks.ConformanceChecking.LTLAnalyzer import LTLAnalyzer

analyzer = LTLAnalyzer(event_log, [model_1, model_2])
conf_check_res_df = analyzer.run_multiple_models(minimize_automaton=False)

The output is a Pandas DataFrame that can be easily queried.

[4]:
print(f"Accepted traces: {len(conf_check_res_df[conf_check_res_df['accepted'] == True])}")
conf_check_res_df
Accepted traces: 957
[4]:
case:concept:name accepted
0 A True
1 B True
2 C True
3 D True
4 E True
... ... ...
1045 HNA True
1046 INA False
1047 JNA False
1048 KNA True
1049 LNA False

1050 rows × 2 columns

The same easy coding holds also for Branched-DECLARE templates:

[5]:
template = LTLTemplate('alternate_response')
activities_a = ["Er Registration", "IV Liquid"]
activities_b = ["CRP", "IV Antibiotics"]
model_1 = template.fill_template(activities_a, activities_b)

template = LTLTemplate('not_precedence')
activities_a = ["ER Sepsis Triage", "CRP"]
activities_b = ["IV Antibiotics", "LacticAcid"]
model_2 = template.fill_template(activities_a, activities_b)

analyzer = LTLAnalyzer(event_log, [model_1, model_2])
conf_check_res_df = analyzer.run_multiple_models()

We therefore access the Pandas Dataframe

[6]:
print(f"Accepted traces: {len(conf_check_res_df[conf_check_res_df['accepted'] == True])}")
conf_check_res_df
Accepted traces: 117
[6]:
case:concept:name accepted
0 A False
1 B False
2 C False
3 D False
4 E False
... ... ...
1045 HNA False
1046 INA False
1047 JNA False
1048 KNA False
1049 LNA False

1050 rows × 2 columns

More information about managing process models and the LTLf or B-Declare templates can be found in tutorials 2 and 3.