Skip to content

Commit 4776272

Browse files
committed
try using hljs
1 parent 124824a commit 4776272

File tree

1 file changed

+91
-21
lines changed

1 file changed

+91
-21
lines changed

_posts/2022-09-06-ant-colony-optimization-tsp.md

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ importance: 8
99
sitemap: true
1010
---
1111

12+
{% raw %}
13+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css">
14+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
15+
{% endraw %}
16+
1217
Ant Colony Optimization algorithms always intrigued me. They are loosely based in biology and the real protocols ants use to communicate and plan routes. They do this by coordinating through small pheromone messages: chemical trails they leave as they move forward, signaling for other ants to follow them. Even though each ant is not especially smart, and they follow simple rules individually, collectively they can converge to complex behaviors as a system, and amazing properties emerge.
1318

1419
In the computational sense, Ant Colony Optimization algorithms solve complex optimization problems for which a closed-form or polynomial solution does not exist, by trying different "routes" across some relevant space or graph, and trying to find the most efficient one (typically the shortest) from two points that satisfies some constraints.
@@ -87,7 +92,41 @@ That is, the probability of choosing a certain edge will be proportional to:
8792

8893
Where P is the level of pheromones in that edge, and D the distance the edge covers. To get the distribution we sample from at each random jump, we normalize these weight coefficients so they add up to one.
8994
<div class="wide-eighty">
90-
{% raw %}<div id="gist1"></div>{% endraw %}
95+
{% raw %}
96+
<pre><code class="language-python">
97+
def traverse_graph(g, source_node = 0):
98+
ALPHA = 0.9
99+
BETA = 1.5
100+
visited = np.asarray([1 for _ in range(g.nodes)]) #originally no nodes have been visited
101+
visited[source_node] = 0 # except the initial/source node.
102+
103+
cycle = [source_node]
104+
steps = 0
105+
current = source_node
106+
total_length = 0
107+
while steps < g.nodes -1:
108+
109+
jumps_neighbors = []
110+
jumps_values = []
111+
for node in range(g.nodes):
112+
if visited[node] != 0:
113+
pheromone_level = max(g.intensity[current][node], 1e-5) #constant added to encourage exploration
114+
v = (pheromone_level**ALPHA ) / (g.distance[current][node]**BETA)
115+
jumps_neighbors.append(node)
116+
jumps_values.append(v)
117+
118+
next_node = random.choices(jumps_neighbors, weights = jumps_values)[0] # weighted (normalized) choice
119+
120+
visited[next_node] = 0
121+
current = next_node
122+
cycle.append(current)
123+
steps+=1
124+
125+
total_length = cycle_length(g, cycle) # just adds all the distances
126+
assert len(list(set(cycle))) == len(cycle)
127+
return cycle, total_length
128+
</code></pre>
129+
{% endraw %}
91130
</div>
92131
After that, the optimization procedure itself consists of:
93132

@@ -107,7 +146,51 @@ Additionally, I tried a few more modifications to the algorithm: the 'elite' or
107146
Here is the whole function in all its glory (with comments for sanity).
108147

109148
<div class="wide-eighty">
110-
{% raw %}<div id="gist2"></div>{% endraw %}
149+
{% raw %}
150+
<pre><code class="language-python">
151+
def ant_colony_optimization(g, verbose=True, iterations = 100, ants_per_iteration = 50, q = 10, degradation_factor = .9, use_inertia = False):
152+
best_cycle = best_so_far # can be pre set or set to None
153+
best_length = cycle_length(g, best_so_far) #hardcoded instance. Else use None
154+
if use_inertia: #this is adding pheromones everywhere if the process stagnates. This did not improve my results and is left off.
155+
old_best = None
156+
inertia = 0
157+
patience = 100
158+
159+
for iteration in range(iterations):
160+
cycles = [traverse_graph(g, random.randint(0, g.nodes -1)) for _ in range(ants_per_iteration)] # could be trivially parallelized if not on Mac through multiprocessing
161+
cycles.sort(key = lambda x: x[1])
162+
cycles = cycles[: ants_per_iteration//2] #optionally keep best half.
163+
164+
if best_cycle: #elitism
165+
cycles.append((best_cycle, best_length))
166+
if use_inertia:
167+
old_best = best_length
168+
169+
for cycle, total_length in cycles: # pheromone update
170+
total_length = cycle_length(g, cycle)
171+
if total_length < best_length:
172+
best_length = total_length
173+
best_cycle = cycle
174+
175+
delta = q/total_length
176+
i = 0
177+
while i < len(cycle) -1:
178+
g.intensity[cycle[i]][cycle[i+1]]+= delta
179+
i+=1
180+
g.intensity[cycle[i]][cycle[0]] += delta
181+
g.intensity *= degradation_factor
182+
183+
if use_inertia and best_cycle:
184+
if old_best == best_length:
185+
inertia+=1
186+
else:
187+
inertia = 0
188+
if inertia > patience:
189+
g.intensity += g.intensity.mean() # applying shake
190+
191+
return best_cycle
192+
</code></pre>
193+
{% endraw %}
111194
</div>
112195

113196
Some possible improvements for this algorithm that I didn't have the time for:
@@ -183,24 +266,11 @@ I would like to try Ant Colony Optimization for problems other than TSP in the f
183266

184267
{% raw %}
185268
<script>
186-
function setUpGists() {
187-
const url1 = "https://gist.github.com/StrikingLoo/432302f114822d24504cf6bab0ab3964.js";
188-
const url2 ="https://gist.github.com/StrikingLoo/778db2438b18d38f126082c046b19acd.js";
189-
190-
const container1 = document.createElement('div');
191-
container1.id = 'gist-container-1';
192-
document.getElementById('gist1').appendChild(container1);
193-
const script1 = document.createElement('script');
194-
script1.src = url1;
195-
container1.appendChild(script1);
196-
197-
const container2 = document.createElement('div');
198-
container2.id = 'gist-container-2';
199-
document.getElementById('gist2').appendChild(container2);
200-
const script2 = document.createElement('script');
201-
script2.src = url2;
202-
container2.appendChild(script2);
203-
}
204-
window.addEventListener('DOMContentLoaded', setUpGists);
269+
document.addEventListener('DOMContentLoaded', (event) => {
270+
document.querySelectorAll('pre code').forEach((el) => {
271+
hljs.highlightElement(el);
272+
});
273+
});
205274
</script>
275+
206276
{% endraw %}

0 commit comments

Comments
 (0)