From 553c757b58699d942045881d1790c6bd08e29ff8 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 8 Apr 2025 13:58:29 -0400 Subject: [PATCH 01/20] Add "generate_random_grids" in scripts. --- scripts/generate_random_grids.py | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 scripts/generate_random_grids.py diff --git a/scripts/generate_random_grids.py b/scripts/generate_random_grids.py new file mode 100644 index 0000000..f64b277 --- /dev/null +++ b/scripts/generate_random_grids.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Generate random grids/graphs with obstacles. +""" + +import argparse +import os +from my_research.utils.grid_dijkstra import (generer_grille, save_graph) + +def _build_arg_parser(): + p = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawTextHelpFormatter) + p.add_argument('--size', type=int, default=10, help="size of the grid (use an int)") + p.add_argument('--obstacles', type=str, default='ratio:0.2', + help="Obstacle settings: 'ratio:' or 'number:'") + p.add_argument("--output", type=str, required=True, + help="Name of the file and number of graphs saved: 'graph.json;'") + return p + +def main(): + parser = _build_arg_parser() + args = parser.parse_args() + + + mode, value = args.obstacles.split(':') + value = float(value) if mode == 'ratio' else int(value) + + if mode == 'ratio' and value > 1: + raise ValueError(f'The obstacle ratio should not be higher than 1.0 (received: {args.obstacles})') + + + file, number = args.output.split(';') + number = int(number) + + + grid, G = generer_grille(args.size, mode, value) if mode == "ratio" else generer_grille(args.size, mode, value, value) + + + for i in range(number): + save_graph(G, f"{file}_{i}.json") + + print(f"✅ {number} graphs saved as '{file}_X.json'.") + print("Grille générée :") + print(grid) + print("Graph nodes:", list(G.nodes)) + +if __name__ == "__main__": + main() From 4b008e7cae673439cd0d0fa71b67ce13f43bce62 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 8 Apr 2025 14:02:46 -0400 Subject: [PATCH 02/20] Create random_grid_generator creation of the fil "random_grid_generator". The file is still empty. --- quactography/classical/utils/random_grid_generator | 1 + 1 file changed, 1 insertion(+) create mode 100644 quactography/classical/utils/random_grid_generator diff --git a/quactography/classical/utils/random_grid_generator b/quactography/classical/utils/random_grid_generator new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/quactography/classical/utils/random_grid_generator @@ -0,0 +1 @@ + From 57894e146fe02859e9221b61f7da1282ede57f7b Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 8 Apr 2025 14:06:31 -0400 Subject: [PATCH 03/20] Update and rename random_grid_generator to random_grid_generator.py add generer_grille. --- .../classical/utils/random_grid_generator | 1 - .../classical/utils/random_grid_generator.py | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) delete mode 100644 quactography/classical/utils/random_grid_generator create mode 100644 quactography/classical/utils/random_grid_generator.py diff --git a/quactography/classical/utils/random_grid_generator b/quactography/classical/utils/random_grid_generator deleted file mode 100644 index 8b13789..0000000 --- a/quactography/classical/utils/random_grid_generator +++ /dev/null @@ -1 +0,0 @@ - diff --git a/quactography/classical/utils/random_grid_generator.py b/quactography/classical/utils/random_grid_generator.py new file mode 100644 index 0000000..31431e2 --- /dev/null +++ b/quactography/classical/utils/random_grid_generator.py @@ -0,0 +1,21 @@ +def generer_grille(size, obstacle_mode="ratio", obstacle_ratio=0.2, obstacle_number=20): + n = size + grid = np.zeros((n, n)) + G = nx.grid_2d_graph(n, n) + + obstacles = set() + + if obstacle_mode == "ratio": + num_obstacles = int(n * n * obstacle_ratio) + else: + num_obstacles = obstacle_number + + while len(obstacles) < num_obstacles: + x, y = random.randint(0, n-1), random.randint(0, n-1) + if (x, y) != (0, 0) and (x, y) != (n-1, n-1): + obstacles.add((x, y)) + grid[x, y] = 1 # Ajouter un obstacle + if (x, y) in G: + G.remove_node((x, y)) + + return grid, G From 793e1796771769f78de10acf9d3ff1a01f469fd0 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 8 Apr 2025 15:35:34 -0400 Subject: [PATCH 04/20] Update generate_random_grids.py impoving on the strucure of the code with Flake8 --- scripts/generate_random_grids.py | 50 +++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/scripts/generate_random_grids.py b/scripts/generate_random_grids.py index f64b277..4b47228 100644 --- a/scripts/generate_random_grids.py +++ b/scripts/generate_random_grids.py @@ -6,45 +6,61 @@ """ import argparse -import os -from my_research.utils.grid_dijkstra import (generer_grille, save_graph) +from my_research.utils.grid_dijkstra import generer_grille, save_graph + def _build_arg_parser(): p = argparse.ArgumentParser( - description=__doc__, formatter_class=argparse.RawTextHelpFormatter) - p.add_argument('--size', type=int, default=10, help="size of the grid (use an int)") - p.add_argument('--obstacles', type=str, default='ratio:0.2', - help="Obstacle settings: 'ratio:' or 'number:'") - p.add_argument("--output", type=str, required=True, - help="Name of the file and number of graphs saved: 'graph.json;'") + description=__doc__, + formatter_class=argparse.RawTextHelpFormatter + ) + p.add_argument( + '--size', type=int, default=10, + help="Size of the grid (grid will be of shape size x size)." + ) + p.add_argument( + '--obstacles', type=str, default='ratio:0.2', + help="Obstacle settings: 'ratio:' or 'number:'" + ) + p.add_argument( + '--output', type=str, required=True, + help="Output format: 'filename.json;'. " + "This will generate files like 'filename_0.json', etc." + ) return p + def main(): parser = _build_arg_parser() args = parser.parse_args() - mode, value = args.obstacles.split(':') value = float(value) if mode == 'ratio' else int(value) - - if mode == 'ratio' and value > 1: - raise ValueError(f'The obstacle ratio should not be higher than 1.0 (received: {args.obstacles})') - + if mode == 'ratio' and not (0 <= value <= 1): + raise ValueError( + "The obstacle ratio must be between 0 and 1" + f"(received: {args.obstacles})" + ) + file, number = args.output.split(';') number = int(number) - - grid, G = generer_grille(args.size, mode, value) if mode == "ratio" else generer_grille(args.size, mode, value, value) + # Générer une seule grille pour tous les graphes + grid, G = ( + generer_grille(args.size, mode, value) + if mode == "ratio" + else generer_grille(args.size, mode, value, value) + ) - for i in range(number): save_graph(G, f"{file}_{i}.json") - + print(f"✅ {number} graphs saved as '{file}_X.json'.") print("Grille générée :") print(grid) print("Graph nodes:", list(G.nodes)) + if __name__ == "__main__": main() From 900beb13d991de81a50a30b0263789af29179c69 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 8 Apr 2025 15:43:01 -0400 Subject: [PATCH 05/20] Create io.py Added save_graph --- quactography/classical/io.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 quactography/classical/io.py diff --git a/quactography/classical/io.py b/quactography/classical/io.py new file mode 100644 index 0000000..a72088d --- /dev/null +++ b/quactography/classical/io.py @@ -0,0 +1,10 @@ +def save_graph(G, output_base, copies=1): + for i in range(copies): + output_file = f"{output_base}_{i}.json" + data = { + "nodes": list(G.nodes()), + "edges": list(G.edges()) + } + with open(output_file, "w") as f: + json.dump(data, f, indent=4) + print(f"✅ Copie {i+1}/{copies} saved as '{output_file}'.") From 7b32020404bbf316ad1111fd93b687831daacf1b Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 8 Apr 2025 16:14:25 -0400 Subject: [PATCH 06/20] Update generate_random_grids.py improving the structure of the code --- scripts/generate_random_grids.py | 54 +++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/scripts/generate_random_grids.py b/scripts/generate_random_grids.py index 4b47228..e9e4d20 100644 --- a/scripts/generate_random_grids.py +++ b/scripts/generate_random_grids.py @@ -3,6 +3,9 @@ """ Generate random grids/graphs with obstacles. +This script can optionally suppress the output +display of the grid and graph nodes. +The generated graphs are saved as .json files. """ import argparse @@ -10,57 +13,70 @@ def _build_arg_parser(): - p = argparse.ArgumentParser( + parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawTextHelpFormatter ) - p.add_argument( + + parser.add_argument( '--size', type=int, default=10, help="Size of the grid (grid will be of shape size x size)." ) - p.add_argument( + + parser.add_argument( '--obstacles', type=str, default='ratio:0.2', help="Obstacle settings: 'ratio:' or 'number:'" ) - p.add_argument( + + parser.add_argument( '--output', type=str, required=True, help="Output format: 'filename.json;'. " "This will generate files like 'filename_0.json', etc." ) - return p + parser.add_argument( + '--quiet', action='store_true', + help="If set, suppress grid and node outputs." + ) -def main(): - parser = _build_arg_parser() - args = parser.parse_args() + return parser - mode, value = args.obstacles.split(':') - value = float(value) if mode == 'ratio' else int(value) + +def parse_obstacle_mode(obstacle_str): + mode, value_str = obstacle_str.split(':') + value = float(value_str) if mode == 'ratio' else int(value_str) if mode == 'ratio' and not (0 <= value <= 1): raise ValueError( - "The obstacle ratio must be between 0 and 1" - f"(received: {args.obstacles})" - ) + f"The obstacle ratio must be between 0 and 1 (received: {value})") + + return mode, value + + +def main(): + parser = _build_arg_parser() + args = parser.parse_args() + + mode, value = parse_obstacle_mode(args.obstacles) file, number = args.output.split(';') number = int(number) - # Générer une seule grille pour tous les graphes grid, G = ( generer_grille(args.size, mode, value) if mode == "ratio" else generer_grille(args.size, mode, value, value) ) + if not args.quiet: + print(f"{number} graphs saved as '{file}_X.json'.") + print("Grille générée :") + print(grid) + print("Graph nodes:", list(G.nodes)) + for i in range(number): save_graph(G, f"{file}_{i}.json") - print(f"✅ {number} graphs saved as '{file}_X.json'.") - print("Grille générée :") - print(grid) - print("Graph nodes:", list(G.nodes)) - if __name__ == "__main__": main() From 5bdc2cfb31687c78bebd70cb1ec0aeef69d09dc2 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 8 Apr 2025 16:19:29 -0400 Subject: [PATCH 07/20] Update random_grid_generator.py Add docstring --- .../classical/utils/random_grid_generator.py | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/quactography/classical/utils/random_grid_generator.py b/quactography/classical/utils/random_grid_generator.py index 31431e2..d905570 100644 --- a/quactography/classical/utils/random_grid_generator.py +++ b/quactography/classical/utils/random_grid_generator.py @@ -1,21 +1,54 @@ +import numpy as np +import random +import networkx as nx + def generer_grille(size, obstacle_mode="ratio", obstacle_ratio=0.2, obstacle_number=20): + """ + Generate a random 2D grid and its corresponding NetworkX graph. + + Parameters + ---------- + size : int + Size of the grid (the grid will be of shape `size x size`). + Must be a positive integer. + obstacle_mode : str + Strategy used to place obstacles in the grid. Options are: + - "ratio": place a proportion of obstacles based on `obstacle_ratio` + - "number": place a fixed number of obstacles based on `obstacle_number` + obstacle_ratio : float + Used only if `obstacle_mode` is "ratio". Defines the proportion of cells + to be turned into obstacles. Must be a float between 0 and 1. + obstacle_number : int + Used only if `obstacle_mode` is "number". Defines the exact number of obstacles + to place in the grid. Must be a positive integer. + + Returns + ------- + grid : np.ndarray + A 2D NumPy array of shape `(size, size)` representing the grid, where `1` + denotes an obstacle and `0` a free cell. + G : networkx.Graph + A 2D grid graph where each node is a tuple `(x, y)`. Edges connect 4-neighboring + nodes (up, down, left, right). Nodes corresponding to obstacles are removed. + """ n = size - grid = np.zeros((n, n)) - G = nx.grid_2d_graph(n, n) + grid = np.zeros((n, n)) + G = nx.grid_2d_graph(n, n) obstacles = set() - if obstacle_mode == "ratio": + if obstacle_mode == "ratio": num_obstacles = int(n * n * obstacle_ratio) else: num_obstacles = obstacle_number while len(obstacles) < num_obstacles: - x, y = random.randint(0, n-1), random.randint(0, n-1) - if (x, y) != (0, 0) and (x, y) != (n-1, n-1): + x, y = random.randint(0, n - 1), random.randint(0, n - 1) + if (x, y) != (0, 0) and (x, y) != (n - 1, n - 1): obstacles.add((x, y)) - grid[x, y] = 1 # Ajouter un obstacle + grid[x, y] = 1 # Add an obstacle if (x, y) in G: G.remove_node((x, y)) - + return grid, G + From e483bbcd9391f0ab82860d99960b7c7abfd9c42d Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 8 Apr 2025 16:24:33 -0400 Subject: [PATCH 08/20] Update io.py save_graph saves in npz format --- quactography/classical/io.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/quactography/classical/io.py b/quactography/classical/io.py index a72088d..667ac63 100644 --- a/quactography/classical/io.py +++ b/quactography/classical/io.py @@ -1,10 +1,15 @@ +import numpy as np + def save_graph(G, output_base, copies=1): for i in range(copies): - output_file = f"{output_base}_{i}.json" - data = { - "nodes": list(G.nodes()), - "edges": list(G.edges()) - } - with open(output_file, "w") as f: - json.dump(data, f, indent=4) + output_file = f"{output_base}_{i}.npz" + nodes = list(G.nodes()) + edges = list(G.edges()) + + nodes_array = np.array(nodes) + edges_array = np.array(edges) + + np.savez(output_file, nodes=nodes_array, edges=edges_array) + print(f"✅ Copie {i+1}/{copies} saved as '{output_file}'.") + From 0ee10514f66d4f3137eae5f49f0ec54973575bf2 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 8 Apr 2025 16:27:44 -0400 Subject: [PATCH 09/20] Update generate_random_grids.py The graphs are now saved as .npz (might not work). --- scripts/generate_random_grids.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/generate_random_grids.py b/scripts/generate_random_grids.py index e9e4d20..d1e5533 100644 --- a/scripts/generate_random_grids.py +++ b/scripts/generate_random_grids.py @@ -5,7 +5,7 @@ Generate random grids/graphs with obstacles. This script can optionally suppress the output display of the grid and graph nodes. -The generated graphs are saved as .json files. +The generated graphs are saved as .npz files. """ import argparse @@ -30,12 +30,12 @@ def _build_arg_parser(): parser.add_argument( '--output', type=str, required=True, - help="Output format: 'filename.json;'. " - "This will generate files like 'filename_0.json', etc." + help="Output format: 'filename.npz;'. " + "This will generate files like 'filename_0.npz', etc." ) parser.add_argument( - '--quiet', action='store_true', + '--save_only', action='store_true', help="If set, suppress grid and node outputs." ) @@ -68,14 +68,14 @@ def main(): else generer_grille(args.size, mode, value, value) ) - if not args.quiet: - print(f"{number} graphs saved as '{file}_X.json'.") + if not args.save_only: + print(f"{number} graphs saved as '{file}_X.npz'.") print("Grille générée :") print(grid) print("Graph nodes:", list(G.nodes)) for i in range(number): - save_graph(G, f"{file}_{i}.json") + save_graph(G, f"{file}_{i}.npz") if __name__ == "__main__": From 0582607b00985a3718a942bfa0113bcff8251505 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 8 Apr 2025 16:37:47 -0400 Subject: [PATCH 10/20] Update io.py delete unecessary --- quactography/classical/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quactography/classical/io.py b/quactography/classical/io.py index 667ac63..16f30d4 100644 --- a/quactography/classical/io.py +++ b/quactography/classical/io.py @@ -11,5 +11,5 @@ def save_graph(G, output_base, copies=1): np.savez(output_file, nodes=nodes_array, edges=edges_array) - print(f"✅ Copie {i+1}/{copies} saved as '{output_file}'.") + print(f"Copie {i+1}/{copies} saved as '{output_file}'.") From 2d7c05994c29bd367bdfdb6dbb309664c7ab212c Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 22 Apr 2025 13:29:34 -0400 Subject: [PATCH 11/20] Update io.py Add description to save_graph --- quactography/classical/io.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/quactography/classical/io.py b/quactography/classical/io.py index 16f30d4..81e58c9 100644 --- a/quactography/classical/io.py +++ b/quactography/classical/io.py @@ -1,8 +1,35 @@ import numpy as np def save_graph(G, output_base, copies=1): + """ + Save the structure of a 2D grid graph into compressed `.npz` files. + + Parameters + ---------- + G : networkx.Graph + A 2D grid graph where each node is represented as a tuple `(x, y)`. + The graph typically connects nodes to their 4-neighbors (up, down, left, right). + Nodes corresponding to obstacles should already be removed from the graph. + output_base : str + Base name for the output files. Each saved file will be named as + "_.npz". + copies : int, optional (default=1) + Number of identical copies of the graph to save. + + Returns + ------- + None + Files are saved to disk in NumPy's compressed `.npz` format, containing: + - 'nodes': array of node coordinates + - 'edges': array of edge pairs (as tuples of coordinates) + + Example + ------- + >>> save_graph(G, "grid_graph", copies=3) + Saves: grid_graph_0.npz, grid_graph_1.npz, grid_graph_2.npz + """ for i in range(copies): - output_file = f"{output_base}_{i}.npz" + output_file = f"{output_base}_{i}.npz" nodes = list(G.nodes()) edges = list(G.edges()) @@ -10,6 +37,4 @@ def save_graph(G, output_base, copies=1): edges_array = np.array(edges) np.savez(output_file, nodes=nodes_array, edges=edges_array) - - print(f"Copie {i+1}/{copies} saved as '{output_file}'.") - + print(f"Copy {i+1}/{copies} saved as '{output_file}'.") From 31877c57acd49f5eaedb618a76cb0642f7dfe517 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 22 Apr 2025 13:32:18 -0400 Subject: [PATCH 12/20] Update generate_random_grids.py delete type=str for the output --- scripts/generate_random_grids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_random_grids.py b/scripts/generate_random_grids.py index d1e5533..8ed92db 100644 --- a/scripts/generate_random_grids.py +++ b/scripts/generate_random_grids.py @@ -29,7 +29,7 @@ def _build_arg_parser(): ) parser.add_argument( - '--output', type=str, required=True, + '--output', required=True, help="Output format: 'filename.npz;'. " "This will generate files like 'filename_0.npz', etc." ) From 863fbe480704dbf93b02e719d110124d2befd3d8 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 22 Apr 2025 14:56:36 -0400 Subject: [PATCH 13/20] Update generate_random_grids.py mutually exclusive --- scripts/generate_random_grids.py | 74 ++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/scripts/generate_random_grids.py b/scripts/generate_random_grids.py index 8ed92db..1ffd06e 100644 --- a/scripts/generate_random_grids.py +++ b/scripts/generate_random_grids.py @@ -9,6 +9,8 @@ """ import argparse +import sys + from my_research.utils.grid_dijkstra import generer_grille, save_graph @@ -20,63 +22,79 @@ def _build_arg_parser(): parser.add_argument( '--size', type=int, default=10, - help="Size of the grid (grid will be of shape size x size)." + help="Size of the grid (the grid will be of shape size x size)." ) - parser.add_argument( - '--obstacles', type=str, default='ratio:0.2', - help="Obstacle settings: 'ratio:' or 'number:'" + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument( + '--ratio', type=float, + help="Ratio of obstacles (e.g., 0.2 = 20%)." + ) + group.add_argument( + '--number', type=int, + help="Exact number of obstacles." ) parser.add_argument( '--output', required=True, help="Output format: 'filename.npz;'. " - "This will generate files like 'filename_0.npz', etc." + "Generates files like 'filename_0.npz', etc." ) parser.add_argument( '--save_only', action='store_true', - help="If set, suppress grid and node outputs." + help="If set, suppresses grid and node outputs." ) return parser -def parse_obstacle_mode(obstacle_str): - mode, value_str = obstacle_str.split(':') - value = float(value_str) if mode == 'ratio' else int(value_str) - - if mode == 'ratio' and not (0 <= value <= 1): +def parse_output_arg(output_str): + try: + file, number_str = output_str.split(';') + number = int(number_str) + if number <= 0: + raise ValueError("The number of files must be greater than 0.") + return file, number + except ValueError: raise ValueError( - f"The obstacle ratio must be between 0 and 1 (received: {value})") - - return mode, value + f"Invalid output format: '{output_str}'. " + "Expected format is 'filename.npz;'." + ) def main(): parser = _build_arg_parser() args = parser.parse_args() - mode, value = parse_obstacle_mode(args.obstacles) + try: + file, number = parse_output_arg(args.output) + except ValueError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) - file, number = args.output.split(';') - number = int(number) + mode = 'ratio' if args.ratio is not None else 'number' + value = args.ratio if args.ratio is not None else args.number - grid, G = ( - generer_grille(args.size, mode, value) - if mode == "ratio" - else generer_grille(args.size, mode, value, value) - ) - - if not args.save_only: - print(f"{number} graphs saved as '{file}_X.npz'.") - print("Grille générée :") - print(grid) - print("Graph nodes:", list(G.nodes)) + if mode == 'ratio' and not (0 <= value <= 1): + print("Error: Ratio must be between 0 and 1.", file=sys.stderr) + sys.exit(1) for i in range(number): + grid, G = ( + generer_grille(args.size, 'ratio', value) + if mode == 'ratio' + else generer_grille(args.size, 'number', value, value) + ) + save_graph(G, f"{file}_{i}.npz") + if not args.save_only: + print(f"Graph {i + 1}/{number} saved as '{file}_{i}.npz'") + print("Generated grid:") + print(grid) + print("Graph nodes:", list(G.nodes)) + if __name__ == "__main__": main() From 663401e76888ce1bf0580bce7b99b02ec262f503 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 22 Apr 2025 15:01:15 -0400 Subject: [PATCH 14/20] Update generate_random_grids.py functions path changed --- scripts/generate_random_grids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_random_grids.py b/scripts/generate_random_grids.py index 1ffd06e..a0f9423 100644 --- a/scripts/generate_random_grids.py +++ b/scripts/generate_random_grids.py @@ -11,7 +11,7 @@ import argparse import sys -from my_research.utils.grid_dijkstra import generer_grille, save_graph +from quactography.classical.utils.random_grid_generator import generate_grid, save_graph def _build_arg_parser(): From b9e7068a3aa02dbb9611e33bebf12770774671eb Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 22 Apr 2025 15:04:40 -0400 Subject: [PATCH 15/20] Update generate_random_grids.py --- scripts/generate_random_grids.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generate_random_grids.py b/scripts/generate_random_grids.py index a0f9423..6b15b9f 100644 --- a/scripts/generate_random_grids.py +++ b/scripts/generate_random_grids.py @@ -82,9 +82,9 @@ def main(): for i in range(number): grid, G = ( - generer_grille(args.size, 'ratio', value) + generate_grid(args.size, 'ratio', value) if mode == 'ratio' - else generer_grille(args.size, 'number', value, value) + else generate_grid(args.size, 'number', value, value) ) save_graph(G, f"{file}_{i}.npz") From 99df3f93bdbf560c10d98a9c1d8188e949965474 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 22 Apr 2025 15:23:36 -0400 Subject: [PATCH 16/20] Update generate_random_grids.py --- scripts/generate_random_grids.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generate_random_grids.py b/scripts/generate_random_grids.py index 6b15b9f..61499e8 100644 --- a/scripts/generate_random_grids.py +++ b/scripts/generate_random_grids.py @@ -11,8 +11,8 @@ import argparse import sys -from quactography.classical.utils.random_grid_generator import generate_grid, save_graph - +from quactography.classical.utils.random_grid_generator import generate_grid +from quactography.classical.io import save_graph def _build_arg_parser(): parser = argparse.ArgumentParser( From 7f24e407b054ac182cfd1b1c76006294cb8c72a7 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 22 Apr 2025 15:28:45 -0400 Subject: [PATCH 17/20] Update generate_random_grids.py print changes --- scripts/generate_random_grids.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/generate_random_grids.py b/scripts/generate_random_grids.py index 61499e8..590d18e 100644 --- a/scripts/generate_random_grids.py +++ b/scripts/generate_random_grids.py @@ -87,13 +87,14 @@ def main(): else generate_grid(args.size, 'number', value, value) ) - save_graph(G, f"{file}_{i}.npz") + save_graph(G, f"{file}_{i}.npz") if not args.save_only: print(f"Graph {i + 1}/{number} saved as '{file}_{i}.npz'") - print("Generated grid:") - print(grid) - print("Graph nodes:", list(G.nodes)) + if not args.save_only: + print("Generated grid:") + print(grid) + print("Graph nodes:", list(G.nodes)) if __name__ == "__main__": From 6e028ec15070f5eb4ede1a095442c312cbc94136 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 22 Apr 2025 15:33:17 -0400 Subject: [PATCH 18/20] Update generate_random_grids.py Flake8 --- scripts/generate_random_grids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate_random_grids.py b/scripts/generate_random_grids.py index 590d18e..3fa4808 100644 --- a/scripts/generate_random_grids.py +++ b/scripts/generate_random_grids.py @@ -87,7 +87,7 @@ def main(): else generate_grid(args.size, 'number', value, value) ) - save_graph(G, f"{file}_{i}.npz") + save_graph(G, f"{file}_{i}.npz") if not args.save_only: print(f"Graph {i + 1}/{number} saved as '{file}_{i}.npz'") From fe93b1d3cbaecd5cf722bf927893aad58366cdb0 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 22 Apr 2025 15:52:44 -0400 Subject: [PATCH 19/20] Create shortest_pathfinder.py --- scripts/shortest_pathfinder.py | 111 +++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 scripts/shortest_pathfinder.py diff --git a/scripts/shortest_pathfinder.py b/scripts/shortest_pathfinder.py new file mode 100644 index 0000000..6b0cc0c --- /dev/null +++ b/scripts/shortest_pathfinder.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Find the shortest path between 2 points +in a graph using Dijkstra or A* algorithm. +Supports graphs loaded from JSON or NPZ files, +and optionally allows diagonal movement. +""" + +import argparse +import os +import sys +from quactography.classical.utils.random_Dijkstra import dijkstra_stepwise +from quactography.classical.utils.random_Astar import astar_stepwise, heuristic +from quactography.classical.io import load_the_graph + + +def _build_arg_parser(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawTextHelpFormatter + ) + parser.add_argument( + "--in_graph", type=str, required=True, + help="Path to the input graph file (.json or .npz)" + ) + parser.add_argument( + "--shortestpath", choices=['Dijkstra', 'A*'], default='Dijkstra', + help="Shortest path algorithm to use: 'Dijkstra' or 'A*'" + ) + parser.add_argument( + "--start", type=str, required=True, + help="Start node, e.g. '3,4'" + ) + parser.add_argument( + "--target", type=str, required=True, + help="Target node, e.g. '7,8'" + ) + parser.add_argument( + "--diagonal_mode", choices=['diagonal', 'nondiagonal'], + default='nondiagonal', + help="Allow diagonal movement or not" + ) + return parser + + +def parse_node(node_str): + try: + parts = node_str.strip().split(',') + return tuple(int(p) for p in parts if p.strip() != '') + except ValueError as e: + raise ValueError( + f"Invalid node format: '{node_str}' (expected format: x,y)" + ) from e + + +def main(): + parser = _build_arg_parser() + args = parser.parse_args() + + if not os.path.isfile(args.in_graph): + print(f"Error: File '{args.in_graph}' not found.") + sys.exit(1) + + try: + start = parse_node(args.start) + target = parse_node(args.target) + except ValueError as e: + print(f"Error parsing node: {e}") + sys.exit(1) + + G = load_the_graph(args.in_graph) + + if start not in G.nodes(): + print(f"Start node {start} not in graph.") + print(f"Available nodes: {list(G.nodes())[:5]}...") + sys.exit(1) + + if target not in G.nodes(): + print(f"Target node {target} not in graph.") + print(f"Available nodes: {list(G.nodes())[:5]}...") + sys.exit(1) + + print( + f"Finding shortest path from {start} to {target} using {args.shortestpath}..." + ) + + if args.shortestpath == "Dijkstra": + evaluated_nodes, path_history, path_cost = dijkstra_stepwise( + G, start, target, args.diagonal_mode + ) + else: + evaluated_nodes, path_history, path_cost = astar_stepwise( + G, start, target, args.diagonal_mode + ) + + if path_history is None: + print("⚠️ Aucun chemin trouvé.") + sys.exit(0) + + shortest_path = [tuple(int(x) for x in n) for n in path_history[-1]] + + print("\nShortest path:") + print(" → ".join(map(str, shortest_path))) + print(f"Path cost: {path_cost:.2f}") + print(f"Nodes evaluated: {len(evaluated_nodes)}") + + +if __name__ == "__main__": + main() From a67f9bce71c1f7f39ba4ed7b932b7528ee59c710 Mon Sep 17 00:00:00 2001 From: Thierry56511 Date: Tue, 22 Apr 2025 15:54:03 -0400 Subject: [PATCH 20/20] Create Dijkstra.py --- quactography/classical/utils/Dijkstra.py | 52 ++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 quactography/classical/utils/Dijkstra.py diff --git a/quactography/classical/utils/Dijkstra.py b/quactography/classical/utils/Dijkstra.py new file mode 100644 index 0000000..0119621 --- /dev/null +++ b/quactography/classical/utils/Dijkstra.py @@ -0,0 +1,52 @@ +def dijkstra_stepwise(G, start, target, diagonal_mode="nondiagonal"): + start_time = time.time() + distances = {node: float('inf') for node in G.nodes()} + distances[start] = 0 + previous_nodes = {node: None for node in G.nodes()} + evaluated_nodes = [] + path_to_current = [] + priority_queue = [(0, start)] + heapq.heapify(priority_queue) + + while priority_queue: + current_distance, current_node = heapq.heappop(priority_queue) + if current_node not in evaluated_nodes: + evaluated_nodes.append(current_node) + + # Reconstruction du chemin actuel + temp_path = [] + node = current_node + while node is not None: + temp_path.append(node) + if node in previous_nodes: # ✅ Vérification pour éviter KeyError + node = previous_nodes[node] + else: + break # ✅ Stopper si le nœud n'est pas connu + temp_path.reverse() + path_to_current.append(temp_path) + + if current_node == target: + break + + if diagonal_mode == "diagonal": + neighbors = list(get_neighbors_diagonal(current_node, G)) + else: + neighbors = list(G.neighbors(current_node)) + + for neighbor in neighbors: + if neighbor not in evaluated_nodes: + edge_weight = G[current_node][neighbor].get("weight", 1) # Récupérer le poids réel + new_distance = current_distance + edge_weight + if new_distance < distances[neighbor]: + distances[neighbor] = new_distance + previous_nodes[neighbor] = current_node + heapq.heappush(priority_queue, (new_distance, neighbor)) + + if distances[target] == float('inf'): + print("⚠️ Aucun chemin trouvé entre le point de départ et l'arrivée.") + return None, None # Retourner None pour indiquer l'absence de chemin + + end_time = time.time() + execution_time = end_time - start_time + print(f"Execution time of Dijkstra: {execution_time:.4f} secondes") + return evaluated_nodes, path_to_current, current_distance