This document describes the modernization of the Ant Colony Optimization project from the original implementation to modern idiomatic Clojure.
Original:
- Used Leiningen with
project.clj - Clojure 1.8.0
- Seesaw 1.4.5 (Swing wrapper)
Modern:
- Uses Clojure CLI tools with
deps.edn - Clojure 1.12.0 (latest stable)
- Quil 4.3.1563 (Processing wrapper for visualizations)
- tools.cli 1.1.230 (Standard CLI argument parsing)
- Modern test runner integration
- JVM options configured to suppress warnings
Original:
- Global mutable state with
atomat top level - Mixed concerns in single namespace
- Imperative style in places
Modern:
- Explicit state management
- Clear separation of concerns (core algorithm vs UI)
- Functional style throughout
- State passed explicitly or managed locally
Original:
(def number-of-ants 500)
(def number-of-generations 125)
(defn length-of-connection ...)
(defn length-of-tour-internal ...)Modern:
(def default-config
{:num-ants 500
:num-generations 125})
(defn connection-distance ...)
(defn tour-length ...)Original:
- Functions with side effects mixed with pure functions
- Global atom mutations
- Inconsistent parameter ordering
Modern:
- Pure functions separated from side effects
- Explicit state threading
- Consistent parameter ordering (data first, config last)
- Better use of destructuring
Original:
(def shortest-tour (atom {:tour-length Long/MAX_VALUE :tour []}))Modern:
(defn create-state []
{:shortest-tour {:length Long/MAX_VALUE :path []}
:generation 0})Original:
(defn one-generation-ant-tours [number-of-ants number-of-nodes distance-data connection-data generation]
(let [tour-list (pmap (fn [ant] (walk-ant-tour distance-data connection-data number-of-nodes)) (range number-of-ants))
generation-shortest-tour (apply min-key :tour-length tour-list)
new-connection-data (-> connection-data (adjust-pheromone-for-multiple-tours tour-list) evaporate-all-connections)]
(print "Generation:" generation)
(when (< (:tour-length generation-shortest-tour) (:tour-length @shortest-tour))
(print " Length:" (:tour-length generation-shortest-tour))
(reset! shortest-tour generation-shortest-tour))
(println)
new-connection-data))Modern:
(defn process-generation
"Process one generation of ants."
[state connections distances config]
(let [{:keys [num-ants]} config
num-nodes (count (set (mapcat identity (keys connections))))
tours (pmap (fn [_] (walk-ant distances connections num-nodes))
(range num-ants))
best-tour (apply min-key :length tours)
new-connections (-> connections
(deposit-generation-pheromone tours config)
(evaporate-all config))
new-state (-> state
(update :generation inc)
(update :shortest-tour
(fn [current]
(if (< (:length best-tour) (:length current))
best-tour
current))))]
{:state new-state
:connections new-connections}))Original:
- Minimal docstrings
- No namespace documentation
- Limited comments
Modern:
- Comprehensive docstrings for all public functions
- Namespace-level documentation
- Clear parameter descriptions
- Usage examples in README
Original:
(deftest test-euclidean-distance
(is (= 0.0 (euclidean-distance [0 0] [0 0])))
(is (= 5.0 (euclidean-distance [0 0] [4 3]))))Modern:
(deftest euclidean-distance-test
(testing "Euclidean distance calculation"
(is (= 0.0 (aco/euclidean-distance [0 0] [0 0])))
(is (= 5.0 (aco/euclidean-distance [0 0] [4 3])))
(is (= 5.0 (aco/euclidean-distance [4 3] [0 0])))))Original (Seesaw/Swing):
- Direct atom manipulation from UI
- Mixed rendering and state logic
- Global state coupling
- Swing-based components
Modern (Quil/Processing):
- Local UI state with
defonce - Functional-mode middleware for state management
- Animation loop with setup/update/draw lifecycle
- Real-time visualization with 30 FPS
- Semi-transparent info panel to prevent overlap
- Better suited for graphics and animations
- JVM options configured to suppress warnings
Original:
- Global
deffor each parameter - Hard to override
- No configuration map
Modern:
(def default-config
{:alpha 1.0
:beta 2.0
:rho 0.25
:num-ants 500
:num-generations 125})
(defn optimize
([nodes] (optimize nodes default-config))
([nodes config]
(let [config (merge default-config config)]
...)))- Memoization: Tour length calculation is memoized
- Parallel Processing: Uses
pmapfor concurrent ant tours - Efficient Data Structures: Uses vectors and maps appropriately
- Transducers: Used where applicable for efficient transformations
-
API Changes:
antopt→optimize:tour-length→:length:tour→:path
-
Configuration:
- Parameters now passed as config map instead of global vars
-
State Management:
- No global mutable state
- State must be passed explicitly or managed locally
To migrate existing code:
- Replace
antoptcalls withoptimize - Pass configuration as a map instead of modifying global vars
- Update result access:
(:tour-length result)→(:length result) - Update result access:
(:tour result)→(:path result)
If Clojure CLI tools are not installed, you can still verify the code structure:
- Check syntax: All files are valid Clojure
- Review test coverage: Comprehensive test suite included
- Verify dependencies: All dependencies are available on Maven Central
- Code review: Modern idiomatic patterns throughout
Potential improvements for future versions:
- Add spec validation for inputs
- Implement additional ACO variants (ACS, MMAS)
- Add benchmarking utilities
- Support for additional TSP file formats
- Web-based visualization with ClojureScript and Reagent
- Performance profiling tools
- Multi-objective optimization support
- Interactive parameter tuning in UI
- Export visualization as video/GIF
Original:
- Custom manual argument parsing with loops
- ~40 lines of imperative code
- No built-in help
- Manual error handling
Modern:
(def cli-options
[["-f" "--file PATH" "Path to TSM file"
:default "resources/bier127.tsm"]
["-a" "--ants N" "Number of ants per generation"
:default 500
:parse-fn #(Integer/parseInt %)
:validate [pos? "Must be a positive number"]]
["-g" "--generations N" "Number of generations to run"
:default 125
:parse-fn #(Integer/parseInt %)
:validate [pos? "Must be a positive number"]]
["-h" "--help" "Show this help message"]])Benefits:
- Uses standard
tools.clilibrary - Declarative option specification
- Automatic help generation
- Built-in validation
- Better error messages
- ~15 lines of code
This modernization brings the codebase up to current Clojure best practices while maintaining the core algorithm's effectiveness. The code is now more maintainable, testable, and idiomatic.