Skip to content

Latest commit

 

History

History
281 lines (226 loc) · 7.7 KB

File metadata and controls

281 lines (226 loc) · 7.7 KB

Migration Notes: Original to Modern Idiomatic Clojure

Overview

This document describes the modernization of the Ant Colony Optimization project from the original implementation to modern idiomatic Clojure.

Key Improvements

1. Project Structure

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

2. Code Organization

Original:

  • Global mutable state with atom at 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

3. Naming Conventions

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 ...)

4. Function Design

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

5. Data Structures

Original:

(def shortest-tour (atom {:tour-length Long/MAX_VALUE :tour []}))

Modern:

(defn create-state []
  {:shortest-tour {:length Long/MAX_VALUE :path []}
   :generation 0})

6. Algorithm Implementation

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}))

7. Documentation

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

8. Testing

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])))))

9. UI Implementation

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

10. Configuration Management

Original:

  • Global def for 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)]
     ...)))

Performance Considerations

  1. Memoization: Tour length calculation is memoized
  2. Parallel Processing: Uses pmap for concurrent ant tours
  3. Efficient Data Structures: Uses vectors and maps appropriately
  4. Transducers: Used where applicable for efficient transformations

Breaking Changes

  1. API Changes:

    • antoptoptimize
    • :tour-length:length
    • :tour:path
  2. Configuration:

    • Parameters now passed as config map instead of global vars
  3. State Management:

    • No global mutable state
    • State must be passed explicitly or managed locally

Migration Path

To migrate existing code:

  1. Replace antopt calls with optimize
  2. Pass configuration as a map instead of modifying global vars
  3. Update result access: (:tour-length result)(:length result)
  4. Update result access: (:tour result)(:path result)

Testing Without Clojure CLI

If Clojure CLI tools are not installed, you can still verify the code structure:

  1. Check syntax: All files are valid Clojure
  2. Review test coverage: Comprehensive test suite included
  3. Verify dependencies: All dependencies are available on Maven Central
  4. Code review: Modern idiomatic patterns throughout

Future Enhancements

Potential improvements for future versions:

  1. Add spec validation for inputs
  2. Implement additional ACO variants (ACS, MMAS)
  3. Add benchmarking utilities
  4. Support for additional TSP file formats
  5. Web-based visualization with ClojureScript and Reagent
  6. Performance profiling tools
  7. Multi-objective optimization support
  8. Interactive parameter tuning in UI
  9. Export visualization as video/GIF

Conclusion

11. CLI Argument Parsing

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.cli library
  • 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.