traveling_salesman.py 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434
  1. """
  2. =================================
  3. Travelling Salesman Problem (TSP)
  4. =================================
  5. Implementation of approximate algorithms
  6. for solving and approximating the TSP problem.
  7. Categories of algorithms which are implemented:
  8. - Christofides (provides a 3/2-approximation of TSP)
  9. - Greedy
  10. - Simulated Annealing (SA)
  11. - Threshold Accepting (TA)
  12. - Asadpour Asymmetric Traveling Salesman Algorithm
  13. The Travelling Salesman Problem tries to find, given the weight
  14. (distance) between all points where a salesman has to visit, the
  15. route so that:
  16. - The total distance (cost) which the salesman travels is minimized.
  17. - The salesman returns to the starting point.
  18. - Note that for a complete graph, the salesman visits each point once.
  19. The function `travelling_salesman_problem` allows for incomplete
  20. graphs by finding all-pairs shortest paths, effectively converting
  21. the problem to a complete graph problem. It calls one of the
  22. approximate methods on that problem and then converts the result
  23. back to the original graph using the previously found shortest paths.
  24. TSP is an NP-hard problem in combinatorial optimization,
  25. important in operations research and theoretical computer science.
  26. http://en.wikipedia.org/wiki/Travelling_salesman_problem
  27. """
  28. import math
  29. import networkx as nx
  30. from networkx.algorithms.tree.mst import random_spanning_tree
  31. from networkx.utils import not_implemented_for, pairwise, py_random_state
  32. __all__ = [
  33. "traveling_salesman_problem",
  34. "christofides",
  35. "asadpour_atsp",
  36. "greedy_tsp",
  37. "simulated_annealing_tsp",
  38. "threshold_accepting_tsp",
  39. ]
  40. def swap_two_nodes(soln, seed):
  41. """Swap two nodes in `soln` to give a neighbor solution.
  42. Parameters
  43. ----------
  44. soln : list of nodes
  45. Current cycle of nodes
  46. seed : integer, random_state, or None (default)
  47. Indicator of random number generation state.
  48. See :ref:`Randomness<randomness>`.
  49. Returns
  50. -------
  51. list
  52. The solution after move is applied. (A neighbor solution.)
  53. Notes
  54. -----
  55. This function assumes that the incoming list `soln` is a cycle
  56. (that the first and last element are the same) and also that
  57. we don't want any move to change the first node in the list
  58. (and thus not the last node either).
  59. The input list is changed as well as returned. Make a copy if needed.
  60. See Also
  61. --------
  62. move_one_node
  63. """
  64. a, b = seed.sample(range(1, len(soln) - 1), k=2)
  65. soln[a], soln[b] = soln[b], soln[a]
  66. return soln
  67. def move_one_node(soln, seed):
  68. """Move one node to another position to give a neighbor solution.
  69. The node to move and the position to move to are chosen randomly.
  70. The first and last nodes are left untouched as soln must be a cycle
  71. starting at that node.
  72. Parameters
  73. ----------
  74. soln : list of nodes
  75. Current cycle of nodes
  76. seed : integer, random_state, or None (default)
  77. Indicator of random number generation state.
  78. See :ref:`Randomness<randomness>`.
  79. Returns
  80. -------
  81. list
  82. The solution after move is applied. (A neighbor solution.)
  83. Notes
  84. -----
  85. This function assumes that the incoming list `soln` is a cycle
  86. (that the first and last element are the same) and also that
  87. we don't want any move to change the first node in the list
  88. (and thus not the last node either).
  89. The input list is changed as well as returned. Make a copy if needed.
  90. See Also
  91. --------
  92. swap_two_nodes
  93. """
  94. a, b = seed.sample(range(1, len(soln) - 1), k=2)
  95. soln.insert(b, soln.pop(a))
  96. return soln
  97. @not_implemented_for("directed")
  98. def christofides(G, weight="weight", tree=None):
  99. """Approximate a solution of the traveling salesman problem
  100. Compute a 3/2-approximation of the traveling salesman problem
  101. in a complete undirected graph using Christofides [1]_ algorithm.
  102. Parameters
  103. ----------
  104. G : Graph
  105. `G` should be a complete weighted undirected graph.
  106. The distance between all pairs of nodes should be included.
  107. weight : string, optional (default="weight")
  108. Edge data key corresponding to the edge weight.
  109. If any edge does not have this attribute the weight is set to 1.
  110. tree : NetworkX graph or None (default: None)
  111. A minimum spanning tree of G. Or, if None, the minimum spanning
  112. tree is computed using :func:`networkx.minimum_spanning_tree`
  113. Returns
  114. -------
  115. list
  116. List of nodes in `G` along a cycle with a 3/2-approximation of
  117. the minimal Hamiltonian cycle.
  118. References
  119. ----------
  120. .. [1] Christofides, Nicos. "Worst-case analysis of a new heuristic for
  121. the travelling salesman problem." No. RR-388. Carnegie-Mellon Univ
  122. Pittsburgh Pa Management Sciences Research Group, 1976.
  123. """
  124. # Remove selfloops if necessary
  125. loop_nodes = nx.nodes_with_selfloops(G)
  126. try:
  127. node = next(loop_nodes)
  128. except StopIteration:
  129. pass
  130. else:
  131. G = G.copy()
  132. G.remove_edge(node, node)
  133. G.remove_edges_from((n, n) for n in loop_nodes)
  134. # Check that G is a complete graph
  135. N = len(G) - 1
  136. # This check ignores selfloops which is what we want here.
  137. if any(len(nbrdict) != N for n, nbrdict in G.adj.items()):
  138. raise nx.NetworkXError("G must be a complete graph.")
  139. if tree is None:
  140. tree = nx.minimum_spanning_tree(G, weight=weight)
  141. L = G.copy()
  142. L.remove_nodes_from([v for v, degree in tree.degree if not (degree % 2)])
  143. MG = nx.MultiGraph()
  144. MG.add_edges_from(tree.edges)
  145. edges = nx.min_weight_matching(L, weight=weight)
  146. MG.add_edges_from(edges)
  147. return _shortcutting(nx.eulerian_circuit(MG))
  148. def _shortcutting(circuit):
  149. """Remove duplicate nodes in the path"""
  150. nodes = []
  151. for u, v in circuit:
  152. if v in nodes:
  153. continue
  154. if not nodes:
  155. nodes.append(u)
  156. nodes.append(v)
  157. nodes.append(nodes[0])
  158. return nodes
  159. def traveling_salesman_problem(G, weight="weight", nodes=None, cycle=True, method=None):
  160. """Find the shortest path in `G` connecting specified nodes
  161. This function allows approximate solution to the traveling salesman
  162. problem on networks that are not complete graphs and/or where the
  163. salesman does not need to visit all nodes.
  164. This function proceeds in two steps. First, it creates a complete
  165. graph using the all-pairs shortest_paths between nodes in `nodes`.
  166. Edge weights in the new graph are the lengths of the paths
  167. between each pair of nodes in the original graph.
  168. Second, an algorithm (default: `christofides` for undirected and
  169. `asadpour_atsp` for directed) is used to approximate the minimal Hamiltonian
  170. cycle on this new graph. The available algorithms are:
  171. - christofides
  172. - greedy_tsp
  173. - simulated_annealing_tsp
  174. - threshold_accepting_tsp
  175. - asadpour_atsp
  176. Once the Hamiltonian Cycle is found, this function post-processes to
  177. accommodate the structure of the original graph. If `cycle` is ``False``,
  178. the biggest weight edge is removed to make a Hamiltonian path.
  179. Then each edge on the new complete graph used for that analysis is
  180. replaced by the shortest_path between those nodes on the original graph.
  181. Parameters
  182. ----------
  183. G : NetworkX graph
  184. A possibly weighted graph
  185. nodes : collection of nodes (default=G.nodes)
  186. collection (list, set, etc.) of nodes to visit
  187. weight : string, optional (default="weight")
  188. Edge data key corresponding to the edge weight.
  189. If any edge does not have this attribute the weight is set to 1.
  190. cycle : bool (default: True)
  191. Indicates whether a cycle should be returned, or a path.
  192. Note: the cycle is the approximate minimal cycle.
  193. The path simply removes the biggest edge in that cycle.
  194. method : function (default: None)
  195. A function that returns a cycle on all nodes and approximates
  196. the solution to the traveling salesman problem on a complete
  197. graph. The returned cycle is then used to find a corresponding
  198. solution on `G`. `method` should be callable; take inputs
  199. `G`, and `weight`; and return a list of nodes along the cycle.
  200. Provided options include :func:`christofides`, :func:`greedy_tsp`,
  201. :func:`simulated_annealing_tsp` and :func:`threshold_accepting_tsp`.
  202. If `method is None`: use :func:`christofides` for undirected `G` and
  203. :func:`threshold_accepting_tsp` for directed `G`.
  204. To specify parameters for these provided functions, construct lambda
  205. functions that state the specific value. `method` must have 2 inputs.
  206. (See examples).
  207. Returns
  208. -------
  209. list
  210. List of nodes in `G` along a path with an approximation of the minimal
  211. path through `nodes`.
  212. Raises
  213. ------
  214. NetworkXError
  215. If `G` is a directed graph it has to be strongly connected or the
  216. complete version cannot be generated.
  217. Examples
  218. --------
  219. >>> tsp = nx.approximation.traveling_salesman_problem
  220. >>> G = nx.cycle_graph(9)
  221. >>> G[4][5]["weight"] = 5 # all other weights are 1
  222. >>> tsp(G, nodes=[3, 6])
  223. [3, 2, 1, 0, 8, 7, 6, 7, 8, 0, 1, 2, 3]
  224. >>> path = tsp(G, cycle=False)
  225. >>> path in ([4, 3, 2, 1, 0, 8, 7, 6, 5], [5, 6, 7, 8, 0, 1, 2, 3, 4])
  226. True
  227. Build (curry) your own function to provide parameter values to the methods.
  228. >>> SA_tsp = nx.approximation.simulated_annealing_tsp
  229. >>> method = lambda G, wt: SA_tsp(G, "greedy", weight=wt, temp=500)
  230. >>> path = tsp(G, cycle=False, method=method)
  231. >>> path in ([4, 3, 2, 1, 0, 8, 7, 6, 5], [5, 6, 7, 8, 0, 1, 2, 3, 4])
  232. True
  233. """
  234. if method is None:
  235. if G.is_directed():
  236. method = asadpour_atsp
  237. else:
  238. method = christofides
  239. if nodes is None:
  240. nodes = list(G.nodes)
  241. dist = {}
  242. path = {}
  243. for n, (d, p) in nx.all_pairs_dijkstra(G, weight=weight):
  244. dist[n] = d
  245. path[n] = p
  246. if G.is_directed():
  247. # If the graph is not strongly connected, raise an exception
  248. if not nx.is_strongly_connected(G):
  249. raise nx.NetworkXError("G is not strongly connected")
  250. GG = nx.DiGraph()
  251. else:
  252. GG = nx.Graph()
  253. for u in nodes:
  254. for v in nodes:
  255. if u == v:
  256. continue
  257. GG.add_edge(u, v, weight=dist[u][v])
  258. best_GG = method(GG, weight)
  259. if not cycle:
  260. # find and remove the biggest edge
  261. (u, v) = max(pairwise(best_GG), key=lambda x: dist[x[0]][x[1]])
  262. pos = best_GG.index(u) + 1
  263. while best_GG[pos] != v:
  264. pos = best_GG[pos:].index(u) + 1
  265. best_GG = best_GG[pos:-1] + best_GG[:pos]
  266. best_path = []
  267. for u, v in pairwise(best_GG):
  268. best_path.extend(path[u][v][:-1])
  269. best_path.append(v)
  270. return best_path
  271. @not_implemented_for("undirected")
  272. @py_random_state(2)
  273. def asadpour_atsp(G, weight="weight", seed=None, source=None):
  274. """
  275. Returns an approximate solution to the traveling salesman problem.
  276. This approximate solution is one of the best known approximations for the
  277. asymmetric traveling salesman problem developed by Asadpour et al,
  278. [1]_. The algorithm first solves the Held-Karp relaxation to find a lower
  279. bound for the weight of the cycle. Next, it constructs an exponential
  280. distribution of undirected spanning trees where the probability of an
  281. edge being in the tree corresponds to the weight of that edge using a
  282. maximum entropy rounding scheme. Next we sample that distribution
  283. $2 \\lceil \\ln n \\rceil$ times and save the minimum sampled tree once the
  284. direction of the arcs is added back to the edges. Finally, we augment
  285. then short circuit that graph to find the approximate tour for the
  286. salesman.
  287. Parameters
  288. ----------
  289. G : nx.DiGraph
  290. The graph should be a complete weighted directed graph. The
  291. distance between all paris of nodes should be included and the triangle
  292. inequality should hold. That is, the direct edge between any two nodes
  293. should be the path of least cost.
  294. weight : string, optional (default="weight")
  295. Edge data key corresponding to the edge weight.
  296. If any edge does not have this attribute the weight is set to 1.
  297. seed : integer, random_state, or None (default)
  298. Indicator of random number generation state.
  299. See :ref:`Randomness<randomness>`.
  300. source : node label (default=`None`)
  301. If given, return the cycle starting and ending at the given node.
  302. Returns
  303. -------
  304. cycle : list of nodes
  305. Returns the cycle (list of nodes) that a salesman can follow to minimize
  306. the total weight of the trip.
  307. Raises
  308. ------
  309. NetworkXError
  310. If `G` is not complete or has less than two nodes, the algorithm raises
  311. an exception.
  312. NetworkXError
  313. If `source` is not `None` and is not a node in `G`, the algorithm raises
  314. an exception.
  315. NetworkXNotImplemented
  316. If `G` is an undirected graph.
  317. References
  318. ----------
  319. .. [1] A. Asadpour, M. X. Goemans, A. Madry, S. O. Gharan, and A. Saberi,
  320. An o(log n/log log n)-approximation algorithm for the asymmetric
  321. traveling salesman problem, Operations research, 65 (2017),
  322. pp. 1043–1061
  323. Examples
  324. --------
  325. >>> import networkx as nx
  326. >>> import networkx.algorithms.approximation as approx
  327. >>> G = nx.complete_graph(3, create_using=nx.DiGraph)
  328. >>> nx.set_edge_attributes(G, {(0, 1): 2, (1, 2): 2, (2, 0): 2, (0, 2): 1, (2, 1): 1, (1, 0): 1}, "weight")
  329. >>> tour = approx.asadpour_atsp(G,source=0)
  330. >>> tour
  331. [0, 2, 1, 0]
  332. """
  333. from math import ceil, exp
  334. from math import log as ln
  335. # Check that G is a complete graph
  336. N = len(G) - 1
  337. if N < 2:
  338. raise nx.NetworkXError("G must have at least two nodes")
  339. # This check ignores selfloops which is what we want here.
  340. if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()):
  341. raise nx.NetworkXError("G is not a complete DiGraph")
  342. # Check that the source vertex, if given, is in the graph
  343. if source is not None and source not in G.nodes:
  344. raise nx.NetworkXError("Given source node not in G.")
  345. opt_hk, z_star = held_karp_ascent(G, weight)
  346. # Test to see if the ascent method found an integer solution or a fractional
  347. # solution. If it is integral then z_star is a nx.Graph, otherwise it is
  348. # a dict
  349. if not isinstance(z_star, dict):
  350. # Here we are using the shortcutting method to go from the list of edges
  351. # returned from eularian_circuit to a list of nodes
  352. return _shortcutting(nx.eulerian_circuit(z_star, source=source))
  353. # Create the undirected support of z_star
  354. z_support = nx.MultiGraph()
  355. for u, v in z_star:
  356. if (u, v) not in z_support.edges:
  357. edge_weight = min(G[u][v][weight], G[v][u][weight])
  358. z_support.add_edge(u, v, **{weight: edge_weight})
  359. # Create the exponential distribution of spanning trees
  360. gamma = spanning_tree_distribution(z_support, z_star)
  361. # Write the lambda values to the edges of z_support
  362. z_support = nx.Graph(z_support)
  363. lambda_dict = {(u, v): exp(gamma[(u, v)]) for u, v in z_support.edges()}
  364. nx.set_edge_attributes(z_support, lambda_dict, "weight")
  365. del gamma, lambda_dict
  366. # Sample 2 * ceil( ln(n) ) spanning trees and record the minimum one
  367. minimum_sampled_tree = None
  368. minimum_sampled_tree_weight = math.inf
  369. for _ in range(2 * ceil(ln(G.number_of_nodes()))):
  370. sampled_tree = random_spanning_tree(z_support, "weight", seed=seed)
  371. sampled_tree_weight = sampled_tree.size(weight)
  372. if sampled_tree_weight < minimum_sampled_tree_weight:
  373. minimum_sampled_tree = sampled_tree.copy()
  374. minimum_sampled_tree_weight = sampled_tree_weight
  375. # Orient the edges in that tree to keep the cost of the tree the same.
  376. t_star = nx.MultiDiGraph()
  377. for u, v, d in minimum_sampled_tree.edges(data=weight):
  378. if d == G[u][v][weight]:
  379. t_star.add_edge(u, v, **{weight: d})
  380. else:
  381. t_star.add_edge(v, u, **{weight: d})
  382. # Find the node demands needed to neutralize the flow of t_star in G
  383. node_demands = {n: t_star.out_degree(n) - t_star.in_degree(n) for n in t_star}
  384. nx.set_node_attributes(G, node_demands, "demand")
  385. # Find the min_cost_flow
  386. flow_dict = nx.min_cost_flow(G, "demand")
  387. # Build the flow into t_star
  388. for source, values in flow_dict.items():
  389. for target in values:
  390. if (source, target) not in t_star.edges and values[target] > 0:
  391. # IF values[target] > 0 we have to add that many edges
  392. for _ in range(values[target]):
  393. t_star.add_edge(source, target)
  394. # Return the shortcut eulerian circuit
  395. circuit = nx.eulerian_circuit(t_star, source=source)
  396. return _shortcutting(circuit)
  397. def held_karp_ascent(G, weight="weight"):
  398. """
  399. Minimizes the Held-Karp relaxation of the TSP for `G`
  400. Solves the Held-Karp relaxation of the input complete digraph and scales
  401. the output solution for use in the Asadpour [1]_ ASTP algorithm.
  402. The Held-Karp relaxation defines the lower bound for solutions to the
  403. ATSP, although it does return a fractional solution. This is used in the
  404. Asadpour algorithm as an initial solution which is later rounded to a
  405. integral tree within the spanning tree polytopes. This function solves
  406. the relaxation with the branch and bound method in [2]_.
  407. Parameters
  408. ----------
  409. G : nx.DiGraph
  410. The graph should be a complete weighted directed graph.
  411. The distance between all paris of nodes should be included.
  412. weight : string, optional (default="weight")
  413. Edge data key corresponding to the edge weight.
  414. If any edge does not have this attribute the weight is set to 1.
  415. Returns
  416. -------
  417. OPT : float
  418. The cost for the optimal solution to the Held-Karp relaxation
  419. z : dict or nx.Graph
  420. A symmetrized and scaled version of the optimal solution to the
  421. Held-Karp relaxation for use in the Asadpour algorithm.
  422. If an integral solution is found, then that is an optimal solution for
  423. the ATSP problem and that is returned instead.
  424. References
  425. ----------
  426. .. [1] A. Asadpour, M. X. Goemans, A. Madry, S. O. Gharan, and A. Saberi,
  427. An o(log n/log log n)-approximation algorithm for the asymmetric
  428. traveling salesman problem, Operations research, 65 (2017),
  429. pp. 1043–1061
  430. .. [2] M. Held, R. M. Karp, The traveling-salesman problem and minimum
  431. spanning trees, Operations Research, 1970-11-01, Vol. 18 (6),
  432. pp.1138-1162
  433. """
  434. import numpy as np
  435. from scipy import optimize
  436. def k_pi():
  437. """
  438. Find the set of minimum 1-Arborescences for G at point pi.
  439. Returns
  440. -------
  441. Set
  442. The set of minimum 1-Arborescences
  443. """
  444. # Create a copy of G without vertex 1.
  445. G_1 = G.copy()
  446. minimum_1_arborescences = set()
  447. minimum_1_arborescence_weight = math.inf
  448. # node is node '1' in the Held and Karp paper
  449. n = next(G.__iter__())
  450. G_1.remove_node(n)
  451. # Iterate over the spanning arborescences of the graph until we know
  452. # that we have found the minimum 1-arborescences. My proposed strategy
  453. # is to find the most extensive root to connect to from 'node 1' and
  454. # the least expensive one. We then iterate over arborescences until
  455. # the cost of the basic arborescence is the cost of the minimum one
  456. # plus the difference between the most and least expensive roots,
  457. # that way the cost of connecting 'node 1' will by definition not by
  458. # minimum
  459. min_root = {"node": None, weight: math.inf}
  460. max_root = {"node": None, weight: -math.inf}
  461. for u, v, d in G.edges(n, data=True):
  462. if d[weight] < min_root[weight]:
  463. min_root = {"node": v, weight: d[weight]}
  464. if d[weight] > max_root[weight]:
  465. max_root = {"node": v, weight: d[weight]}
  466. min_in_edge = min(G.in_edges(n, data=True), key=lambda x: x[2][weight])
  467. min_root[weight] = min_root[weight] + min_in_edge[2][weight]
  468. max_root[weight] = max_root[weight] + min_in_edge[2][weight]
  469. min_arb_weight = math.inf
  470. for arb in nx.ArborescenceIterator(G_1):
  471. arb_weight = arb.size(weight)
  472. if min_arb_weight == math.inf:
  473. min_arb_weight = arb_weight
  474. elif arb_weight > min_arb_weight + max_root[weight] - min_root[weight]:
  475. break
  476. # We have to pick the root node of the arborescence for the out
  477. # edge of the first vertex as that is the only node without an
  478. # edge directed into it.
  479. for N, deg in arb.in_degree:
  480. if deg == 0:
  481. # root found
  482. arb.add_edge(n, N, **{weight: G[n][N][weight]})
  483. arb_weight += G[n][N][weight]
  484. break
  485. # We can pick the minimum weight in-edge for the vertex with
  486. # a cycle. If there are multiple edges with the same, minimum
  487. # weight, We need to add all of them.
  488. #
  489. # Delete the edge (N, v) so that we cannot pick it.
  490. edge_data = G[N][n]
  491. G.remove_edge(N, n)
  492. min_weight = min(G.in_edges(n, data=weight), key=lambda x: x[2])[2]
  493. min_edges = [
  494. (u, v, d) for u, v, d in G.in_edges(n, data=weight) if d == min_weight
  495. ]
  496. for u, v, d in min_edges:
  497. new_arb = arb.copy()
  498. new_arb.add_edge(u, v, **{weight: d})
  499. new_arb_weight = arb_weight + d
  500. # Check to see the weight of the arborescence, if it is a
  501. # new minimum, clear all of the old potential minimum
  502. # 1-arborescences and add this is the only one. If its
  503. # weight is above the known minimum, do not add it.
  504. if new_arb_weight < minimum_1_arborescence_weight:
  505. minimum_1_arborescences.clear()
  506. minimum_1_arborescence_weight = new_arb_weight
  507. # We have a 1-arborescence, add it to the set
  508. if new_arb_weight == minimum_1_arborescence_weight:
  509. minimum_1_arborescences.add(new_arb)
  510. G.add_edge(N, n, **edge_data)
  511. return minimum_1_arborescences
  512. def direction_of_ascent():
  513. """
  514. Find the direction of ascent at point pi.
  515. See [1]_ for more information.
  516. Returns
  517. -------
  518. dict
  519. A mapping from the nodes of the graph which represents the direction
  520. of ascent.
  521. References
  522. ----------
  523. .. [1] M. Held, R. M. Karp, The traveling-salesman problem and minimum
  524. spanning trees, Operations Research, 1970-11-01, Vol. 18 (6),
  525. pp.1138-1162
  526. """
  527. # 1. Set d equal to the zero n-vector.
  528. d = {}
  529. for n in G:
  530. d[n] = 0
  531. del n
  532. # 2. Find a 1-Aborescence T^k such that k is in K(pi, d).
  533. minimum_1_arborescences = k_pi()
  534. while True:
  535. # Reduce K(pi) to K(pi, d)
  536. # Find the arborescence in K(pi) which increases the lest in
  537. # direction d
  538. min_k_d_weight = math.inf
  539. min_k_d = None
  540. for arborescence in minimum_1_arborescences:
  541. weighted_cost = 0
  542. for n, deg in arborescence.degree:
  543. weighted_cost += d[n] * (deg - 2)
  544. if weighted_cost < min_k_d_weight:
  545. min_k_d_weight = weighted_cost
  546. min_k_d = arborescence
  547. # 3. If sum of d_i * v_{i, k} is greater than zero, terminate
  548. if min_k_d_weight > 0:
  549. return d, min_k_d
  550. # 4. d_i = d_i + v_{i, k}
  551. for n, deg in min_k_d.degree:
  552. d[n] += deg - 2
  553. # Check that we do not need to terminate because the direction
  554. # of ascent does not exist. This is done with linear
  555. # programming.
  556. c = np.full(len(minimum_1_arborescences), -1, dtype=int)
  557. a_eq = np.empty((len(G) + 1, len(minimum_1_arborescences)), dtype=int)
  558. b_eq = np.zeros(len(G) + 1, dtype=int)
  559. b_eq[len(G)] = 1
  560. for arb_count, arborescence in enumerate(minimum_1_arborescences):
  561. n_count = len(G) - 1
  562. for n, deg in arborescence.degree:
  563. a_eq[n_count][arb_count] = deg - 2
  564. n_count -= 1
  565. a_eq[len(G)][arb_count] = 1
  566. program_result = optimize.linprog(c, A_eq=a_eq, b_eq=b_eq)
  567. # If the constants exist, then the direction of ascent doesn't
  568. if program_result.success:
  569. # There is no direction of ascent
  570. return None, minimum_1_arborescences
  571. # 5. GO TO 2
  572. def find_epsilon(k, d):
  573. """
  574. Given the direction of ascent at pi, find the maximum distance we can go
  575. in that direction.
  576. Parameters
  577. ----------
  578. k_xy : set
  579. The set of 1-arborescences which have the minimum rate of increase
  580. in the direction of ascent
  581. d : dict
  582. The direction of ascent
  583. Returns
  584. -------
  585. float
  586. The distance we can travel in direction `d`
  587. """
  588. min_epsilon = math.inf
  589. for e_u, e_v, e_w in G.edges(data=weight):
  590. if (e_u, e_v) in k.edges:
  591. continue
  592. # Now, I have found a condition which MUST be true for the edges to
  593. # be a valid substitute. The edge in the graph which is the
  594. # substitute is the one with the same terminated end. This can be
  595. # checked rather simply.
  596. #
  597. # Find the edge within k which is the substitute. Because k is a
  598. # 1-arborescence, we know that they is only one such edges
  599. # leading into every vertex.
  600. if len(k.in_edges(e_v, data=weight)) > 1:
  601. raise Exception
  602. sub_u, sub_v, sub_w = next(k.in_edges(e_v, data=weight).__iter__())
  603. k.add_edge(e_u, e_v, **{weight: e_w})
  604. k.remove_edge(sub_u, sub_v)
  605. if (
  606. max(d for n, d in k.in_degree()) <= 1
  607. and len(G) == k.number_of_edges()
  608. and nx.is_weakly_connected(k)
  609. ):
  610. # Ascent method calculation
  611. if d[sub_u] == d[e_u] or sub_w == e_w:
  612. # Revert to the original graph
  613. k.remove_edge(e_u, e_v)
  614. k.add_edge(sub_u, sub_v, **{weight: sub_w})
  615. continue
  616. epsilon = (sub_w - e_w) / (d[e_u] - d[sub_u])
  617. if 0 < epsilon < min_epsilon:
  618. min_epsilon = epsilon
  619. # Revert to the original graph
  620. k.remove_edge(e_u, e_v)
  621. k.add_edge(sub_u, sub_v, **{weight: sub_w})
  622. return min_epsilon
  623. # I have to know that the elements in pi correspond to the correct elements
  624. # in the direction of ascent, even if the node labels are not integers.
  625. # Thus, I will use dictionaries to made that mapping.
  626. pi_dict = {}
  627. for n in G:
  628. pi_dict[n] = 0
  629. del n
  630. original_edge_weights = {}
  631. for u, v, d in G.edges(data=True):
  632. original_edge_weights[(u, v)] = d[weight]
  633. dir_ascent, k_d = direction_of_ascent()
  634. while dir_ascent is not None:
  635. max_distance = find_epsilon(k_d, dir_ascent)
  636. for n, v in dir_ascent.items():
  637. pi_dict[n] += max_distance * v
  638. for u, v, d in G.edges(data=True):
  639. d[weight] = original_edge_weights[(u, v)] + pi_dict[u]
  640. dir_ascent, k_d = direction_of_ascent()
  641. # k_d is no longer an individual 1-arborescence but rather a set of
  642. # minimal 1-arborescences at the maximum point of the polytope and should
  643. # be reflected as such
  644. k_max = k_d
  645. # Search for a cycle within k_max. If a cycle exists, return it as the
  646. # solution
  647. for k in k_max:
  648. if len([n for n in k if k.degree(n) == 2]) == G.order():
  649. # Tour found
  650. return k.size(weight), k
  651. # Write the original edge weights back to G and every member of k_max at
  652. # the maximum point. Also average the number of times that edge appears in
  653. # the set of minimal 1-arborescences.
  654. x_star = {}
  655. size_k_max = len(k_max)
  656. for u, v, d in G.edges(data=True):
  657. edge_count = 0
  658. d[weight] = original_edge_weights[(u, v)]
  659. for k in k_max:
  660. if (u, v) in k.edges():
  661. edge_count += 1
  662. k[u][v][weight] = original_edge_weights[(u, v)]
  663. x_star[(u, v)] = edge_count / size_k_max
  664. # Now symmetrize the edges in x_star and scale them according to (5) in
  665. # reference [1]
  666. z_star = {}
  667. scale_factor = (G.order() - 1) / G.order()
  668. for u, v in x_star:
  669. frequency = x_star[(u, v)] + x_star[(v, u)]
  670. if frequency > 0:
  671. z_star[(u, v)] = scale_factor * frequency
  672. del x_star
  673. # Return the optimal weight and the z dict
  674. return next(k_max.__iter__()).size(weight), z_star
  675. def spanning_tree_distribution(G, z):
  676. """
  677. Find the asadpour exponential distribution of spanning trees.
  678. Solves the Maximum Entropy Convex Program in the Asadpour algorithm [1]_
  679. using the approach in section 7 to build an exponential distribution of
  680. undirected spanning trees.
  681. This algorithm ensures that the probability of any edge in a spanning
  682. tree is proportional to the sum of the probabilities of the tress
  683. containing that edge over the sum of the probabilities of all spanning
  684. trees of the graph.
  685. Parameters
  686. ----------
  687. G : nx.MultiGraph
  688. The undirected support graph for the Held Karp relaxation
  689. z : dict
  690. The output of `held_karp_ascent()`, a scaled version of the Held-Karp
  691. solution.
  692. Returns
  693. -------
  694. gamma : dict
  695. The probability distribution which approximately preserves the marginal
  696. probabilities of `z`.
  697. """
  698. from math import exp
  699. from math import log as ln
  700. def q(e):
  701. """
  702. The value of q(e) is described in the Asadpour paper is "the
  703. probability that edge e will be included in a spanning tree T that is
  704. chosen with probability proportional to exp(gamma(T))" which
  705. basically means that it is the total probability of the edge appearing
  706. across the whole distribution.
  707. Parameters
  708. ----------
  709. e : tuple
  710. The `(u, v)` tuple describing the edge we are interested in
  711. Returns
  712. -------
  713. float
  714. The probability that a spanning tree chosen according to the
  715. current values of gamma will include edge `e`.
  716. """
  717. # Create the laplacian matrices
  718. for u, v, d in G.edges(data=True):
  719. d[lambda_key] = exp(gamma[(u, v)])
  720. G_Kirchhoff = nx.total_spanning_tree_weight(G, lambda_key)
  721. G_e = nx.contracted_edge(G, e, self_loops=False)
  722. G_e_Kirchhoff = nx.total_spanning_tree_weight(G_e, lambda_key)
  723. # Multiply by the weight of the contracted edge since it is not included
  724. # in the total weight of the contracted graph.
  725. return exp(gamma[(e[0], e[1])]) * G_e_Kirchhoff / G_Kirchhoff
  726. # initialize gamma to the zero dict
  727. gamma = {}
  728. for u, v, _ in G.edges:
  729. gamma[(u, v)] = 0
  730. # set epsilon
  731. EPSILON = 0.2
  732. # pick an edge attribute name that is unlikely to be in the graph
  733. lambda_key = "spanning_tree_distribution's secret attribute name for lambda"
  734. while True:
  735. # We need to know that know that no values of q_e are greater than
  736. # (1 + epsilon) * z_e, however changing one gamma value can increase the
  737. # value of a different q_e, so we have to complete the for loop without
  738. # changing anything for the condition to be meet
  739. in_range_count = 0
  740. # Search for an edge with q_e > (1 + epsilon) * z_e
  741. for u, v in gamma:
  742. e = (u, v)
  743. q_e = q(e)
  744. z_e = z[e]
  745. if q_e > (1 + EPSILON) * z_e:
  746. delta = ln(
  747. (q_e * (1 - (1 + EPSILON / 2) * z_e))
  748. / ((1 - q_e) * (1 + EPSILON / 2) * z_e)
  749. )
  750. gamma[e] -= delta
  751. # Check that delta had the desired effect
  752. new_q_e = q(e)
  753. desired_q_e = (1 + EPSILON / 2) * z_e
  754. if round(new_q_e, 8) != round(desired_q_e, 8):
  755. raise nx.NetworkXError(
  756. f"Unable to modify probability for edge ({u}, {v})"
  757. )
  758. else:
  759. in_range_count += 1
  760. # Check if the for loop terminated without changing any gamma
  761. if in_range_count == len(gamma):
  762. break
  763. # Remove the new edge attributes
  764. for _, _, d in G.edges(data=True):
  765. if lambda_key in d:
  766. del d[lambda_key]
  767. return gamma
  768. def greedy_tsp(G, weight="weight", source=None):
  769. """Return a low cost cycle starting at `source` and its cost.
  770. This approximates a solution to the traveling salesman problem.
  771. It finds a cycle of all the nodes that a salesman can visit in order
  772. to visit many nodes while minimizing total distance.
  773. It uses a simple greedy algorithm.
  774. In essence, this function returns a large cycle given a source point
  775. for which the total cost of the cycle is minimized.
  776. Parameters
  777. ----------
  778. G : Graph
  779. The Graph should be a complete weighted undirected graph.
  780. The distance between all pairs of nodes should be included.
  781. weight : string, optional (default="weight")
  782. Edge data key corresponding to the edge weight.
  783. If any edge does not have this attribute the weight is set to 1.
  784. source : node, optional (default: first node in list(G))
  785. Starting node. If None, defaults to ``next(iter(G))``
  786. Returns
  787. -------
  788. cycle : list of nodes
  789. Returns the cycle (list of nodes) that a salesman
  790. can follow to minimize total weight of the trip.
  791. Raises
  792. ------
  793. NetworkXError
  794. If `G` is not complete, the algorithm raises an exception.
  795. Examples
  796. --------
  797. >>> from networkx.algorithms import approximation as approx
  798. >>> G = nx.DiGraph()
  799. >>> G.add_weighted_edges_from({
  800. ... ("A", "B", 3), ("A", "C", 17), ("A", "D", 14), ("B", "A", 3),
  801. ... ("B", "C", 12), ("B", "D", 16), ("C", "A", 13),("C", "B", 12),
  802. ... ("C", "D", 4), ("D", "A", 14), ("D", "B", 15), ("D", "C", 2)
  803. ... })
  804. >>> cycle = approx.greedy_tsp(G, source="D")
  805. >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle))
  806. >>> cycle
  807. ['D', 'C', 'B', 'A', 'D']
  808. >>> cost
  809. 31
  810. Notes
  811. -----
  812. This implementation of a greedy algorithm is based on the following:
  813. - The algorithm adds a node to the solution at every iteration.
  814. - The algorithm selects a node not already in the cycle whose connection
  815. to the previous node adds the least cost to the cycle.
  816. A greedy algorithm does not always give the best solution.
  817. However, it can construct a first feasible solution which can
  818. be passed as a parameter to an iterative improvement algorithm such
  819. as Simulated Annealing, or Threshold Accepting.
  820. Time complexity: It has a running time $O(|V|^2)$
  821. """
  822. # Check that G is a complete graph
  823. N = len(G) - 1
  824. # This check ignores selfloops which is what we want here.
  825. if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()):
  826. raise nx.NetworkXError("G must be a complete graph.")
  827. if source is None:
  828. source = nx.utils.arbitrary_element(G)
  829. if G.number_of_nodes() == 2:
  830. neighbor = next(G.neighbors(source))
  831. return [source, neighbor, source]
  832. nodeset = set(G)
  833. nodeset.remove(source)
  834. cycle = [source]
  835. next_node = source
  836. while nodeset:
  837. nbrdict = G[next_node]
  838. next_node = min(nodeset, key=lambda n: nbrdict[n].get(weight, 1))
  839. cycle.append(next_node)
  840. nodeset.remove(next_node)
  841. cycle.append(cycle[0])
  842. return cycle
  843. @py_random_state(9)
  844. def simulated_annealing_tsp(
  845. G,
  846. init_cycle,
  847. weight="weight",
  848. source=None,
  849. temp=100,
  850. move="1-1",
  851. max_iterations=10,
  852. N_inner=100,
  853. alpha=0.01,
  854. seed=None,
  855. ):
  856. """Returns an approximate solution to the traveling salesman problem.
  857. This function uses simulated annealing to approximate the minimal cost
  858. cycle through the nodes. Starting from a suboptimal solution, simulated
  859. annealing perturbs that solution, occasionally accepting changes that make
  860. the solution worse to escape from a locally optimal solution. The chance
  861. of accepting such changes decreases over the iterations to encourage
  862. an optimal result. In summary, the function returns a cycle starting
  863. at `source` for which the total cost is minimized. It also returns the cost.
  864. The chance of accepting a proposed change is related to a parameter called
  865. the temperature (annealing has a physical analogue of steel hardening
  866. as it cools). As the temperature is reduced, the chance of moves that
  867. increase cost goes down.
  868. Parameters
  869. ----------
  870. G : Graph
  871. `G` should be a complete weighted undirected graph.
  872. The distance between all pairs of nodes should be included.
  873. init_cycle : list of all nodes or "greedy"
  874. The initial solution (a cycle through all nodes returning to the start).
  875. This argument has no default to make you think about it.
  876. If "greedy", use `greedy_tsp(G, weight)`.
  877. Other common starting cycles are `list(G) + [next(iter(G))]` or the final
  878. result of `simulated_annealing_tsp` when doing `threshold_accepting_tsp`.
  879. weight : string, optional (default="weight")
  880. Edge data key corresponding to the edge weight.
  881. If any edge does not have this attribute the weight is set to 1.
  882. source : node, optional (default: first node in list(G))
  883. Starting node. If None, defaults to ``next(iter(G))``
  884. temp : int, optional (default=100)
  885. The algorithm's temperature parameter. It represents the initial
  886. value of temperature
  887. move : "1-1" or "1-0" or function, optional (default="1-1")
  888. Indicator of what move to use when finding new trial solutions.
  889. Strings indicate two special built-in moves:
  890. - "1-1": 1-1 exchange which transposes the position
  891. of two elements of the current solution.
  892. The function called is :func:`swap_two_nodes`.
  893. For example if we apply 1-1 exchange in the solution
  894. ``A = [3, 2, 1, 4, 3]``
  895. we can get the following by the transposition of 1 and 4 elements:
  896. ``A' = [3, 2, 4, 1, 3]``
  897. - "1-0": 1-0 exchange which moves an node in the solution
  898. to a new position.
  899. The function called is :func:`move_one_node`.
  900. For example if we apply 1-0 exchange in the solution
  901. ``A = [3, 2, 1, 4, 3]``
  902. we can transfer the fourth element to the second position:
  903. ``A' = [3, 4, 2, 1, 3]``
  904. You may provide your own functions to enact a move from
  905. one solution to a neighbor solution. The function must take
  906. the solution as input along with a `seed` input to control
  907. random number generation (see the `seed` input here).
  908. Your function should maintain the solution as a cycle with
  909. equal first and last node and all others appearing once.
  910. Your function should return the new solution.
  911. max_iterations : int, optional (default=10)
  912. Declared done when this number of consecutive iterations of
  913. the outer loop occurs without any change in the best cost solution.
  914. N_inner : int, optional (default=100)
  915. The number of iterations of the inner loop.
  916. alpha : float between (0, 1), optional (default=0.01)
  917. Percentage of temperature decrease in each iteration
  918. of outer loop
  919. seed : integer, random_state, or None (default)
  920. Indicator of random number generation state.
  921. See :ref:`Randomness<randomness>`.
  922. Returns
  923. -------
  924. cycle : list of nodes
  925. Returns the cycle (list of nodes) that a salesman
  926. can follow to minimize total weight of the trip.
  927. Raises
  928. ------
  929. NetworkXError
  930. If `G` is not complete the algorithm raises an exception.
  931. Examples
  932. --------
  933. >>> from networkx.algorithms import approximation as approx
  934. >>> G = nx.DiGraph()
  935. >>> G.add_weighted_edges_from({
  936. ... ("A", "B", 3), ("A", "C", 17), ("A", "D", 14), ("B", "A", 3),
  937. ... ("B", "C", 12), ("B", "D", 16), ("C", "A", 13),("C", "B", 12),
  938. ... ("C", "D", 4), ("D", "A", 14), ("D", "B", 15), ("D", "C", 2)
  939. ... })
  940. >>> cycle = approx.simulated_annealing_tsp(G, "greedy", source="D")
  941. >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle))
  942. >>> cycle
  943. ['D', 'C', 'B', 'A', 'D']
  944. >>> cost
  945. 31
  946. >>> incycle = ["D", "B", "A", "C", "D"]
  947. >>> cycle = approx.simulated_annealing_tsp(G, incycle, source="D")
  948. >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle))
  949. >>> cycle
  950. ['D', 'C', 'B', 'A', 'D']
  951. >>> cost
  952. 31
  953. Notes
  954. -----
  955. Simulated Annealing is a metaheuristic local search algorithm.
  956. The main characteristic of this algorithm is that it accepts
  957. even solutions which lead to the increase of the cost in order
  958. to escape from low quality local optimal solutions.
  959. This algorithm needs an initial solution. If not provided, it is
  960. constructed by a simple greedy algorithm. At every iteration, the
  961. algorithm selects thoughtfully a neighbor solution.
  962. Consider $c(x)$ cost of current solution and $c(x')$ cost of a
  963. neighbor solution.
  964. If $c(x') - c(x) <= 0$ then the neighbor solution becomes the current
  965. solution for the next iteration. Otherwise, the algorithm accepts
  966. the neighbor solution with probability $p = exp - ([c(x') - c(x)] / temp)$.
  967. Otherwise the current solution is retained.
  968. `temp` is a parameter of the algorithm and represents temperature.
  969. Time complexity:
  970. For $N_i$ iterations of the inner loop and $N_o$ iterations of the
  971. outer loop, this algorithm has running time $O(N_i * N_o * |V|)$.
  972. For more information and how the algorithm is inspired see:
  973. http://en.wikipedia.org/wiki/Simulated_annealing
  974. """
  975. if move == "1-1":
  976. move = swap_two_nodes
  977. elif move == "1-0":
  978. move = move_one_node
  979. if init_cycle == "greedy":
  980. # Construct an initial solution using a greedy algorithm.
  981. cycle = greedy_tsp(G, weight=weight, source=source)
  982. if G.number_of_nodes() == 2:
  983. return cycle
  984. else:
  985. cycle = list(init_cycle)
  986. if source is None:
  987. source = cycle[0]
  988. elif source != cycle[0]:
  989. raise nx.NetworkXError("source must be first node in init_cycle")
  990. if cycle[0] != cycle[-1]:
  991. raise nx.NetworkXError("init_cycle must be a cycle. (return to start)")
  992. if len(cycle) - 1 != len(G) or len(set(G.nbunch_iter(cycle))) != len(G):
  993. raise nx.NetworkXError("init_cycle should be a cycle over all nodes in G.")
  994. # Check that G is a complete graph
  995. N = len(G) - 1
  996. # This check ignores selfloops which is what we want here.
  997. if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()):
  998. raise nx.NetworkXError("G must be a complete graph.")
  999. if G.number_of_nodes() == 2:
  1000. neighbor = next(G.neighbors(source))
  1001. return [source, neighbor, source]
  1002. # Find the cost of initial solution
  1003. cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(cycle))
  1004. count = 0
  1005. best_cycle = cycle.copy()
  1006. best_cost = cost
  1007. while count <= max_iterations and temp > 0:
  1008. count += 1
  1009. for i in range(N_inner):
  1010. adj_sol = move(cycle, seed)
  1011. adj_cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(adj_sol))
  1012. delta = adj_cost - cost
  1013. if delta <= 0:
  1014. # Set current solution the adjacent solution.
  1015. cycle = adj_sol
  1016. cost = adj_cost
  1017. if cost < best_cost:
  1018. count = 0
  1019. best_cycle = cycle.copy()
  1020. best_cost = cost
  1021. else:
  1022. # Accept even a worse solution with probability p.
  1023. p = math.exp(-delta / temp)
  1024. if p >= seed.random():
  1025. cycle = adj_sol
  1026. cost = adj_cost
  1027. temp -= temp * alpha
  1028. return best_cycle
  1029. @py_random_state(9)
  1030. def threshold_accepting_tsp(
  1031. G,
  1032. init_cycle,
  1033. weight="weight",
  1034. source=None,
  1035. threshold=1,
  1036. move="1-1",
  1037. max_iterations=10,
  1038. N_inner=100,
  1039. alpha=0.1,
  1040. seed=None,
  1041. ):
  1042. """Returns an approximate solution to the traveling salesman problem.
  1043. This function uses threshold accepting methods to approximate the minimal cost
  1044. cycle through the nodes. Starting from a suboptimal solution, threshold
  1045. accepting methods perturb that solution, accepting any changes that make
  1046. the solution no worse than increasing by a threshold amount. Improvements
  1047. in cost are accepted, but so are changes leading to small increases in cost.
  1048. This allows the solution to leave suboptimal local minima in solution space.
  1049. The threshold is decreased slowly as iterations proceed helping to ensure
  1050. an optimum. In summary, the function returns a cycle starting at `source`
  1051. for which the total cost is minimized.
  1052. Parameters
  1053. ----------
  1054. G : Graph
  1055. `G` should be a complete weighted undirected graph.
  1056. The distance between all pairs of nodes should be included.
  1057. init_cycle : list or "greedy"
  1058. The initial solution (a cycle through all nodes returning to the start).
  1059. This argument has no default to make you think about it.
  1060. If "greedy", use `greedy_tsp(G, weight)`.
  1061. Other common starting cycles are `list(G) + [next(iter(G))]` or the final
  1062. result of `simulated_annealing_tsp` when doing `threshold_accepting_tsp`.
  1063. weight : string, optional (default="weight")
  1064. Edge data key corresponding to the edge weight.
  1065. If any edge does not have this attribute the weight is set to 1.
  1066. source : node, optional (default: first node in list(G))
  1067. Starting node. If None, defaults to ``next(iter(G))``
  1068. threshold : int, optional (default=1)
  1069. The algorithm's threshold parameter. It represents the initial
  1070. threshold's value
  1071. move : "1-1" or "1-0" or function, optional (default="1-1")
  1072. Indicator of what move to use when finding new trial solutions.
  1073. Strings indicate two special built-in moves:
  1074. - "1-1": 1-1 exchange which transposes the position
  1075. of two elements of the current solution.
  1076. The function called is :func:`swap_two_nodes`.
  1077. For example if we apply 1-1 exchange in the solution
  1078. ``A = [3, 2, 1, 4, 3]``
  1079. we can get the following by the transposition of 1 and 4 elements:
  1080. ``A' = [3, 2, 4, 1, 3]``
  1081. - "1-0": 1-0 exchange which moves an node in the solution
  1082. to a new position.
  1083. The function called is :func:`move_one_node`.
  1084. For example if we apply 1-0 exchange in the solution
  1085. ``A = [3, 2, 1, 4, 3]``
  1086. we can transfer the fourth element to the second position:
  1087. ``A' = [3, 4, 2, 1, 3]``
  1088. You may provide your own functions to enact a move from
  1089. one solution to a neighbor solution. The function must take
  1090. the solution as input along with a `seed` input to control
  1091. random number generation (see the `seed` input here).
  1092. Your function should maintain the solution as a cycle with
  1093. equal first and last node and all others appearing once.
  1094. Your function should return the new solution.
  1095. max_iterations : int, optional (default=10)
  1096. Declared done when this number of consecutive iterations of
  1097. the outer loop occurs without any change in the best cost solution.
  1098. N_inner : int, optional (default=100)
  1099. The number of iterations of the inner loop.
  1100. alpha : float between (0, 1), optional (default=0.1)
  1101. Percentage of threshold decrease when there is at
  1102. least one acceptance of a neighbor solution.
  1103. If no inner loop moves are accepted the threshold remains unchanged.
  1104. seed : integer, random_state, or None (default)
  1105. Indicator of random number generation state.
  1106. See :ref:`Randomness<randomness>`.
  1107. Returns
  1108. -------
  1109. cycle : list of nodes
  1110. Returns the cycle (list of nodes) that a salesman
  1111. can follow to minimize total weight of the trip.
  1112. Raises
  1113. ------
  1114. NetworkXError
  1115. If `G` is not complete the algorithm raises an exception.
  1116. Examples
  1117. --------
  1118. >>> from networkx.algorithms import approximation as approx
  1119. >>> G = nx.DiGraph()
  1120. >>> G.add_weighted_edges_from({
  1121. ... ("A", "B", 3), ("A", "C", 17), ("A", "D", 14), ("B", "A", 3),
  1122. ... ("B", "C", 12), ("B", "D", 16), ("C", "A", 13),("C", "B", 12),
  1123. ... ("C", "D", 4), ("D", "A", 14), ("D", "B", 15), ("D", "C", 2)
  1124. ... })
  1125. >>> cycle = approx.threshold_accepting_tsp(G, "greedy", source="D")
  1126. >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle))
  1127. >>> cycle
  1128. ['D', 'C', 'B', 'A', 'D']
  1129. >>> cost
  1130. 31
  1131. >>> incycle = ["D", "B", "A", "C", "D"]
  1132. >>> cycle = approx.threshold_accepting_tsp(G, incycle, source="D")
  1133. >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle))
  1134. >>> cycle
  1135. ['D', 'C', 'B', 'A', 'D']
  1136. >>> cost
  1137. 31
  1138. Notes
  1139. -----
  1140. Threshold Accepting is a metaheuristic local search algorithm.
  1141. The main characteristic of this algorithm is that it accepts
  1142. even solutions which lead to the increase of the cost in order
  1143. to escape from low quality local optimal solutions.
  1144. This algorithm needs an initial solution. This solution can be
  1145. constructed by a simple greedy algorithm. At every iteration, it
  1146. selects thoughtfully a neighbor solution.
  1147. Consider $c(x)$ cost of current solution and $c(x')$ cost of
  1148. neighbor solution.
  1149. If $c(x') - c(x) <= threshold$ then the neighbor solution becomes the current
  1150. solution for the next iteration, where the threshold is named threshold.
  1151. In comparison to the Simulated Annealing algorithm, the Threshold
  1152. Accepting algorithm does not accept very low quality solutions
  1153. (due to the presence of the threshold value). In the case of
  1154. Simulated Annealing, even a very low quality solution can
  1155. be accepted with probability $p$.
  1156. Time complexity:
  1157. It has a running time $O(m * n * |V|)$ where $m$ and $n$ are the number
  1158. of times the outer and inner loop run respectively.
  1159. For more information and how algorithm is inspired see:
  1160. https://doi.org/10.1016/0021-9991(90)90201-B
  1161. See Also
  1162. --------
  1163. simulated_annealing_tsp
  1164. """
  1165. if move == "1-1":
  1166. move = swap_two_nodes
  1167. elif move == "1-0":
  1168. move = move_one_node
  1169. if init_cycle == "greedy":
  1170. # Construct an initial solution using a greedy algorithm.
  1171. cycle = greedy_tsp(G, weight=weight, source=source)
  1172. if G.number_of_nodes() == 2:
  1173. return cycle
  1174. else:
  1175. cycle = list(init_cycle)
  1176. if source is None:
  1177. source = cycle[0]
  1178. elif source != cycle[0]:
  1179. raise nx.NetworkXError("source must be first node in init_cycle")
  1180. if cycle[0] != cycle[-1]:
  1181. raise nx.NetworkXError("init_cycle must be a cycle. (return to start)")
  1182. if len(cycle) - 1 != len(G) or len(set(G.nbunch_iter(cycle))) != len(G):
  1183. raise nx.NetworkXError("init_cycle is not all and only nodes.")
  1184. # Check that G is a complete graph
  1185. N = len(G) - 1
  1186. # This check ignores selfloops which is what we want here.
  1187. if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()):
  1188. raise nx.NetworkXError("G must be a complete graph.")
  1189. if G.number_of_nodes() == 2:
  1190. neighbor = list(G.neighbors(source))[0]
  1191. return [source, neighbor, source]
  1192. # Find the cost of initial solution
  1193. cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(cycle))
  1194. count = 0
  1195. best_cycle = cycle.copy()
  1196. best_cost = cost
  1197. while count <= max_iterations:
  1198. count += 1
  1199. accepted = False
  1200. for i in range(N_inner):
  1201. adj_sol = move(cycle, seed)
  1202. adj_cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(adj_sol))
  1203. delta = adj_cost - cost
  1204. if delta <= threshold:
  1205. accepted = True
  1206. # Set current solution the adjacent solution.
  1207. cycle = adj_sol
  1208. cost = adj_cost
  1209. if cost < best_cost:
  1210. count = 0
  1211. best_cycle = cycle.copy()
  1212. best_cost = cost
  1213. if accepted:
  1214. threshold -= threshold * alpha
  1215. return best_cycle