1+ import re
2+ import pandas as pd
3+ import networkx as nx
4+ import matplotlib .pyplot as plt
5+ import argparse
6+
7+ def main (file_path , verbose = False ): # Add verbose as a parameter with a default value
8+ try :
9+ df = pd .read_excel (file_path )
10+ except FileNotFoundError :
11+ print (f"Error: The file '{ file_path } ' was not found. Please ensure it exists in the specified directory." )
12+ exit ()
13+ except ValueError as e :
14+ print (f"Error: Unable to read the file. Make sure it's a valid Excel file. Details: { e } " )
15+ exit ()
16+
17+ # Check if the DataFrame is empty
18+ if df .empty :
19+ print ("Error: The provided Excel file is empty." )
20+ exit ()
21+
22+ if df .isnull ().all ().all ():
23+ print ("Error: The Excel file contains no valid data." )
24+ exit ()
25+
26+ # Ensure columns have valid names
27+ if df .columns .duplicated ().any ():
28+ print ("Error: The Excel file contains duplicate transaction names in columns." )
29+ exit ()
30+
31+ if any (col == '' for col in df .columns ):
32+ print ("Error: One or more column names in the Excel file are empty." )
33+ exit ()
34+
35+ index = 0
36+ temp = 0
37+
38+ # transaction : other transactions
39+ other = dict ()
40+ transactions = [trnsc_name for trnsc_name in df ]
41+ n_transactions = len (transactions )
42+
43+ for transaction in transactions :
44+ other [transaction ] = []
45+
46+ for transaction in transactions :
47+ for othr in range (n_transactions - 1 ):
48+ other [transaction ].append (transactions [((temp + 1 ) % n_transactions )])
49+ temp += 1
50+ index += 1
51+ temp = index
52+
53+ # Serial schedule corresponding to each transaction
54+ column_wise_tnrcs = {}
55+ for l in range (0 , n_transactions ):
56+ column_wise_tnrcs [transactions [l ]] = []
57+
58+ temp = 0
59+ for l in range (0 , n_transactions ):
60+ column_wise_tnrcs [transactions [l ]].extend (list (filter (lambda x : x != '' , df [transactions [temp ]].dropna ().tolist ())))
61+ temp += 1
62+
63+ # Nodes of dependency graph
64+ nodes = []
65+ # Pairs of transactions having conflict
66+ conflicting_nodes = []
67+ conflicting_pairs = {'r' : ['w' ],
68+ 'w' : ['w' , 'r' ]}
69+
70+ # Finding conflicting pairs
71+ for index , row in df .iterrows ():
72+ for col_name , operation in row .items ():
73+ if pd .notna (operation ):
74+ match = re .match (r'([rw])\((\w+)\)' , operation )
75+ if not match :
76+ print (f"Error: Invalid operation format '{ operation } ' in column '{ col_name } '. Expected 'r(var)' or 'w(var)'." )
77+ exit ()
78+ op = match .group (1 ) # operation
79+ var = match .group (2 ) # variable
80+ if op == 'r' :
81+ conflict_rw = conflicting_pairs [op ][0 ] + '(' + var + ')'
82+ if verbose : # Use the verbose flag to control output
83+ print (f"Transaction { col_name } 's conflicting pair for operation { operation } is { conflict_rw } " )
84+ for other_transactions in range (n_transactions - 1 ):
85+ if conflict_rw in column_wise_tnrcs [other [col_name ][other_transactions ]]:
86+ nodes .append (col_name )
87+ conflicting_nodes .append (tuple ([col_name , other [col_name ][other_transactions ]]))
88+ column_wise_tnrcs [col_name ].remove (operation )
89+ else :
90+ conflict_ww = conflicting_pairs [op ][0 ] + '(' + var + ')'
91+ conflict_wr = conflicting_pairs [op ][1 ] + '(' + var + ')'
92+ if verbose : # Use the verbose flag to control output
93+ print (f"Transaction { col_name } 's conflicting pair for operation { operation } is { conflict_ww } and { conflict_wr } " )
94+
95+ for other_transactions in range (n_transactions - 1 ):
96+ if conflict_ww in column_wise_tnrcs [other [col_name ][other_transactions ]]:
97+ nodes .append (col_name )
98+ conflicting_nodes .append (tuple ([col_name , other [col_name ][other_transactions ]]))
99+ column_wise_tnrcs [col_name ].remove (operation )
100+
101+ for other_transactions in range (n_transactions - 1 ):
102+ if conflict_wr in column_wise_tnrcs [other [col_name ][other_transactions ]]:
103+ nodes .append (col_name )
104+ conflicting_nodes .append (tuple ([col_name , other [col_name ][other_transactions ]]))
105+
106+ # Building dependency graph
107+ G = nx .DiGraph ()
108+ G .add_nodes_from (nodes )
109+ G .add_edges_from (conflicting_nodes )
110+
111+ # Detect if the graph has cycles
112+ try :
113+ cycles = list (nx .simple_cycles (G ))
114+ if cycles :
115+ print ("Detected cycles:" , cycles )
116+ print ("Therefore, the given schedule is not conflict serializable." )
117+ else :
118+ print ("No cycles detected. The given schedule is conflict serializable." )
119+ try :
120+ topological_order = list (nx .topological_sort (G ))
121+ print ("Order of serializability:" , topological_order )
122+ except nx .NetworkXUnfeasible :
123+ print ("Error: Topological sorting failed despite no cycles detected." )
124+ except Exception as e :
125+ print (f"Unexpected error during cycle detection or topological sorting: { e } " )
126+
127+ # Draw the graph
128+ if not G .nodes or not G .edges :
129+ print ("Warning: The dependency graph is empty; nothing to plot." )
130+ else :
131+ pos = nx .spring_layout (G ) # positions for all nodes
132+ nx .draw (G , pos , with_labels = True , node_size = 3000 , node_color = 'lightblue' , font_size = 12 , font_weight = 'bold' , arrowsize = 20 )
133+
134+ # Add text on the plot to indicate if a cycle exists
135+ cycle_text = "Cycle Exists, NOT CSS" if cycles else "No Cycle, CSS"
136+ text_color = 'red' if cycles else 'green'
137+
138+ # Determine the center of the graph to place the text dynamically
139+ x_center = sum (p [0 ] for p in pos .values ()) / len (pos )
140+ y_center = sum (p [1 ] for p in pos .values ()) / len (pos )
141+
142+ plt .text (x_center , y_center - 0.1 , cycle_text , horizontalalignment = 'center' ,
143+ fontsize = 14 , color = text_color , bbox = dict (facecolor = 'white' , alpha = 0.7 ))
144+
145+ plt .title ('Dependency Graph' )
146+ plt .show ()
147+
148+ if __name__ == "__main__" :
149+ # Set up argument parsing
150+ parser = argparse .ArgumentParser (description = "Check if a schedule is conflict serializable." )
151+ parser .add_argument ("file" , type = str , help = "Path to the Excel file containing the schedule." )
152+ parser .add_argument ("--verbose" , action = "store_true" , help = "Enable verbose output for debugging." ) # Add verbose flag
153+
154+ # Parse arguments
155+ args = parser .parse_args ()
156+
157+ # Call the main function with the file path and verbose flag
158+ main (args .file , args .verbose )
0 commit comments