diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index f627f46..d271e6b 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -15,10 +15,14 @@ graphviz https ispell levelname +maxnode +maxtransition nsew padx pady println +pycairo +pylib scrollregion snd xdg diff --git a/README.md b/README.md index 08622e6..153b73b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + @@ -65,7 +65,7 @@ If you are having trouble installing the required Python3 Libraries see [here][p or Download and save locally 2. Install [Cairosvg][cairosvg-package] ```sh - pip install cairosvg + pip install cairosvg ``` 3. Install [Graphviz-pylib][graphviz-package] ```sh diff --git a/src/globals.py b/src/globals.py index a4058ae..9216ca9 100644 --- a/src/globals.py +++ b/src/globals.py @@ -7,8 +7,10 @@ loaded_svg_content = None MIN_WIDTH = 1 MIN_HEIGHT = 1 +MAXIMUM_RECURSION_DEPTH = 15 current_image = None transition_trace: list[str] = [] +graph_states: list[str] = [] state_stack: list[str] = [] is_svg_updated = False hints_visible = False diff --git a/src/graph_analysis.py b/src/graph_analysis.py index 6c750fd..9b00650 100644 --- a/src/graph_analysis.py +++ b/src/graph_analysis.py @@ -1,5 +1,10 @@ from tkinter import messagebox +import globals + +current_best_node = [] +current_best_path = [] + def perform_reachability_analysis(transitions, initial_state): visited = set() @@ -12,6 +17,7 @@ def perform_reachability_analysis(transitions, initial_state): for next_state in transitions.get(state, {}): stack.append(next_state) + globals.graph_states = visited unreachable_states = set(transitions.keys()) - visited return visited, unreachable_states @@ -34,6 +40,78 @@ def on_reachability_analysis(transitions, initial_state_key, show_results): show_results("Reachability Analysis:", results) +def decide_graph_analysis(mode, transitions, initial_state_key, show_results): + if len(globals.graph_states) <= 15: + perform_euler_hamilton_walk(mode, transitions, initial_state_key, show_results) + else: + messagebox.showinfo( + "UML Diagram has too many States", + "Performing longest Path analysis instead", + ) + if mode == "node": + perform_longest_path_analysis(transitions, initial_state_key, show_results) + else: + perform_max_transition_path_analysis( + transitions, initial_state_key, show_results + ) + + +def perform_euler_hamilton_walk(mode, transitions, initial_state_key, show_results): + global current_best_node + global current_best_path + current_best_node = [] + current_best_path = [] + + def find_longest_paths( + current_state, + path, + visited, + recursion_depth, + ): + global current_best_node + global current_best_path + visited.append(current_state) + + if mode == "node": + if len(set(visited)) > len(set(current_best_node)): + current_best_node = visited + current_best_path = path + elif len(set(visited)) == len(set(current_best_node)): + if len(path) < len(current_best_path): + current_best_node = visited + current_best_path = path + elif mode == "transition": + if len(set(path)) > len(set(current_best_path)): + current_best_node = visited + current_best_path = path + elif len(set(path)) == len(set(current_best_path)): + if len(path) < len(current_best_path): + current_best_node = visited + current_best_path = path + + if recursion_depth < globals.MAXIMUM_RECURSION_DEPTH: + for next_state, labels in transitions.get(current_state, {}).items(): + for label in labels: + new_path = path + [(current_state, next_state, label)] + find_longest_paths( + next_state, + new_path, + visited.copy(), + recursion_depth + 1, + ) + + find_longest_paths(initial_state_key, [], [], 0) + + transition_sequences = [] + for path in current_best_path: + transition_sequences.append(path[2]) + + show_results( + "Maximum " + mode + " analysis:", + "\n" + " ".join(current_best_node) + "\n" + "->".join(transition_sequences), + ) + + def perform_longest_path_analysis(transitions, initial_state_key, show_results): def find_longest_paths(current_state, path, visited): visited.add(current_state) @@ -75,10 +153,12 @@ def find_max_transition_path(transitions, current_state, path, visited_transitio ) if len(result_path) > len(max_path): max_path = result_path + print(len(max_path)) return max_path def perform_max_transition_path_analysis(transitions, initial_state_key, show_results): + print("reached1") max_transition_path = find_max_transition_path( transitions, initial_state_key, [], set() ) diff --git a/src/main.py b/src/main.py index a6c34fb..8412b16 100644 --- a/src/main.py +++ b/src/main.py @@ -26,9 +26,9 @@ TRANSITION_TRACE_TITLE_BG, ) from graph_analysis import ( + decide_graph_analysis, on_reachability_analysis, - perform_longest_path_analysis, - perform_max_transition_path_analysis, + perform_reachability_analysis, ) from graph_visualization import show_state_diagram_graph from GUI import ( @@ -95,6 +95,7 @@ def on_file_loaded(): initial_state_key = globals.initial_state_key globals.analysis_results_text.pack_forget() globals.analysis_results_visible = False + perform_reachability_analysis(current_transitions, initial_state_key) def update_text_width(): canvas_width = canvas.winfo_width() @@ -254,8 +255,8 @@ def show_analysis_results(title, content): left_button_frame, text="Max Nodes Analysis", state="disabled", - command=lambda: perform_longest_path_analysis( - current_transitions, initial_state_key, show_analysis_results + command=lambda: decide_graph_analysis( + "node", current_transitions, initial_state_key, show_analysis_results ), ) @@ -263,8 +264,8 @@ def show_analysis_results(title, content): left_button_frame, text="Max transitions Analysis", state="disabled", - command=lambda: perform_max_transition_path_analysis( - current_transitions, initial_state_key, show_analysis_results + command=lambda: decide_graph_analysis( + "transition", current_transitions, initial_state_key, show_analysis_results ), )