3 @author Lorenzo Sciandra
4 @brief First it builds the program
in C, specifying the number of nodes to use
and whether it
is in hybrid mode
or not.
5 Then it runs the graph conv net on the instance,
and finally it runs the Branch
and Bound.
6 It can be run on a single instance
or a range of instances.
7 The input matrix
is generated by the neural network
and stored
in the data folder. The output
is stored
in the results folder.
10 @copyright Copyright (c) 2024, license MIT
12 Repo: https://github.com/LorenzoSciandra/GraphConvolutionalBranchandBound
22def adjacency_matrix(orig_graph):
24 Calculates the adjacency matrix of the graph.
26 orig_graph: The original graph.
29 The adjacency matrix of the graph.
31 adj_matrix = np.zeros((len(orig_graph), len(orig_graph)))
33 for i
in range(0, len(orig_graph)):
34 for j
in range(i + 1, len(orig_graph)):
35 adj_matrix[i][j] = np.linalg.norm(np.array(orig_graph[i]) - np.array(orig_graph[j]))
36 adj_matrix[j][i] = adj_matrix[i][j]
43 Creates a temporary file to store the current instance of the TSP for the neural network.
45 num_nodes: The number of nodes
in the TSP instance.
46 str_grap: The string representation of the graph.
48 filepath = "graph-convnet-tsp/data/hyb_tsp/test_" +
str(num_nodes) +
"_nodes_temp.txt"
50 if not os.path.exists(os.path.dirname(filepath)):
52 os.makedirs(os.path.dirname(filepath))
53 except OSError
as exc:
54 if exc.errno != errno.EEXIST:
57 with open(filepath,
'w+')
as file:
58 file.writelines(str_grap)
60 os.fsync(file.fileno())
65 From a graph, it returns the nodes in a string format.
67 graph: The graph to get the nodes
from.
70 The nodes
in a string format.
75 nodes +=
"\t" +
str(i) +
" : " +
str(node[0]) +
" " +
str(node[1]) +
"\n"
83 Gets the instance of the TSP from the file.
85 instance: The instance to get.
86 num_nodes: The number of nodes
in the TSP instance.
89 The graph
in a list
and string format.
93 file_path =
"graph-convnet-tsp/data/hyb_tsp/test_" +
str(num_nodes) +
"_nodes.txt"
94 with open(file_path,
"r")
as f:
97 if lines
is None or len(lines) < instance - 1:
99 "The instance " +
str(instance) +
" for the number of nodes " +
str(num_nodes) +
" does not exist.")
101 str_graph = lines[instance - 1]
102 orig_graph = str_graph
104 if "output" in str_graph:
105 str_graph = str_graph.split(
" output")[0]
107 str_graph = str_graph.replace(
"\n",
"").strip()
108 nodes = str_graph.split(
" ")
109 graph = [float(x)
for x
in nodes]
110 graph = [[graph[i], graph[i + 1]]
for i
in range(0, len(graph), 2)]
112 return graph, orig_graph
117 Builds the C program with the specified number of nodes
and whether it
is in hybrid mode
or not.
119 build_directory: The directory where the CMakeLists.txt file
is located
and where the executable will be built.
120 num_nodes: The number of nodes to use
in the C program.
121 hyb_mode: 1
if the program
is in hybrid mode, 0 otherwise.
123 source_directory = "../"
126 "-S" + source_directory,
127 "-B" + build_directory,
128 "-DCMAKE_BUILD_TYPE=Release",
129 "-DMAX_VERTEX_NUM=" +
str(num_nodes),
130 "-DHYBRID=" +
str(hyb_mode)
135 "-C" + build_directory,
139 subprocess.check_call(cmake_command)
140 subprocess.check_call(make_command)
141 except subprocess.CalledProcessError
as e:
142 print(
"Build failed:")
144 raise Exception(
"Build failed")
149 The Graph Convolutional Branch-and-Bound Solver.
151 num_instances: The range of instances to run on the Solver.
152 num_nodes: The number of nodes
in each TSP instance.
153 hyb_mode:
True if the program
is in hybrid mode,
False otherwise.
154 gen_matrix:
True if the adjacency matrix
is already generated,
False otherwise.
162 raise Exception(
"The number of nodes must be greater than 1.")
163 elif num_nodes <= 20:
165 elif num_nodes <= 50:
170 model_size = num_nodes
172 build_directory =
"../cmake-build/CMakeFiles/BranchAndBound1Tree.dir"
173 hybrid = 1
if hyb_mode
else 0
176 if "-" in num_instances:
177 instances = num_instances.split(
"-")
178 start_instance = 1
if int(instances[0]) == 0
else int(instances[0])
179 end_instance =
int(instances[1])
182 end_instance =
int(num_instances)
184 print(
"Starting instance: " +
str(start_instance))
185 print(
"Ending instance: " +
str(end_instance))
187 for i
in range(start_instance, end_instance + 1):
188 start_time = time.time()
190 input_file =
"../data/AdjacencyMatrix/tsp_" +
str(num_nodes) +
"_nodes/tsp_test_" +
str(i) +
".csv"
191 absolute_input_path = os.path.abspath(input_file)
193 if not os.path.exists(os.path.dirname(absolute_input_path)):
195 os.makedirs(os.path.dirname(absolute_input_path))
196 except OSError
as exc:
197 if exc.errno != errno.EEXIST:
200 result_mode =
"hybrid" if hyb_mode
else "classic"
201 output_file =
"../results/AdjacencyMatrix/tsp_" +
str(num_nodes) +
"_nodes_" + result_mode \
202 +
"/tsp_result_" +
str(i) +
".txt"
206 absolute_python_path = os.path.abspath(
"./graph-convnet-tsp/main.py")
207 result = subprocess.run(
208 [
'python3', absolute_python_path, absolute_input_path,
str(num_nodes),
str(model_size)],
209 cwd=
"./graph-convnet-tsp", check=
True)
210 if result.returncode == 0:
211 print(
'Neural Network completed successfully on instance ' +
str(i) +
' / ' +
str(end_instance))
213 print(
'Neural Network failed on instance ' +
str(i) +
' / ' +
str(end_instance))
217 with open(absolute_input_path,
"w")
as f:
218 nodes_coord =
";".join([f
"({graph[i][0]}, {graph[i][1]})" for i
in range(len(graph))])
219 f.write(nodes_coord +
"\n")
220 for k
in range(len(adj_matrix)):
221 for j
in range(len(adj_matrix[k])):
222 f.write(f
"({adj_matrix[k][j]}, 0);")
225 absolute_output_path = os.path.abspath(output_file)
226 if not os.path.exists(os.path.dirname(absolute_output_path)):
228 os.makedirs(os.path.dirname(absolute_output_path))
229 except OSError
as exc:
230 if exc.errno != errno.EEXIST:
232 cmd = [build_directory +
"/BranchAndBound1Tree", absolute_input_path, absolute_output_path]
233 result = subprocess.run(cmd)
234 if result.returncode == 0:
235 print(
'Branch-and-Bound completed successfully on instance ' +
str(i) +
' / ' +
str(end_instance))
237 print(
'Branch-and-Bound failed on instance ' +
str(i) +
' / ' +
str(end_instance))
239 end_time = time.time()
243 os.remove(
"graph-convnet-tsp/data/hyb_tsp/test_" +
str(num_nodes) +
"_nodes_temp.txt")
245 with open(output_file,
"a")
as f:
246 f.write(
"\nNodes: \n" + cities)
247 f.write(
"\nTime taken: " +
str(end_time - start_time) +
"s\n")
252if __name__ ==
"__main__":
255 --range_instances: The range of instances to run on the Solver.
256 --num_nodes: The number of nodes in each TSP instance.
257 --hybrid_mode: If present, the program
is in hybrid mode, otherwise it
is in classic mode.
260 parser = argparse.ArgumentParser()
261 parser.add_argument("--range_instances", type=str, default=
"1-1")
262 parser.add_argument(
"--num_nodes", type=int, default=20)
263 parser.add_argument(
"--hybrid", action=
"store_true")
264 opts = parser.parse_args()
266 pp.pprint(vars(opts))
268 gen_matrix = opts.hybrid ==
False
270 hybrid_solver(opts.range_instances, opts.num_nodes, opts.hybrid, gen_matrix)
def get_instance(instance, num_nodes)
def hybrid_solver(num_instances, num_nodes, hyb_mode, gen_matrix)
def build_c_program(build_directory, num_nodes, hyb_mode)
def create_temp_file(num_nodes, str_grap)
def adjacency_matrix(orig_graph)