Thursday, 27 November 2014

Swarm - I

I've read a lot of interesting stuff about swarm-intelligence and a couple of weeks ago the idea suddenly came to implement something in Clojure. The full source code is in Github (the application can be run from the app.clj), here I'd like to show how the story has evolved step by step. The first part of the task was to implement some kind of "vector algebra" to model the moves of entities.


(ns
^{:author mate.magyari
:doc "Simple vector algebra"}
clojure-study.ideas.swarm.vector-algebra
(:require [clojure.test :as test]))
(defn square "Square of"
[num]
(* num num))
(defn v* "Multiplication for vectors"
[scalar a-vector]
{:x (* scalar (:x a-vector))
:y (* scalar (:y a-vector))})
(defn v+ "Sum vector of vectors"
[& vectors]
(apply merge-with + vectors))
(defn v- "Diff vector of vectors"
[v1 v2]
(merge-with - v1 v2))
(defn magnitude [{x :x y :y}] "Length of a vector"
(Math/sqrt
(+ (square x) (square y))))
(defn polar->cartesian
"Transforming a polar vector representation to a cartesian one"
[{angle :angle magnitude :magnitude}]
{:x (* magnitude (Math/cos angle))
:y (* magnitude (Math/sin angle))})
(defn cartesian->polar
"Transforming a cartesian vector representation to a polar one"
[v]
{:angle (Math/atan2 (:y v) (:x v))
:magnitude (magnitude v)})
(defn rotate-cartesian "Rotating a vector"
[v angle]
(-> v
cartesian->polar
(update-in [:angle] + angle)
polar->cartesian))
(defn distance "Distance between 2 points"
[point-from point-to]
(magnitude
(merge-with - point-to point-from)))
(defn null-vector? "Checks whether the vector is a null vector"
[v]
(or (= v {:x 0 :y 0})
(= v {:x 0.0 :y 0.0})))
(defn normalize "Normalize the vector"
[a-vector]
(let [len (magnitude a-vector)]
(if (= 0.0 len)
a-vector ; null vector simply returned
(let [div-len #(/ % len)]
(-> a-vector
(update-in [:x] div-len)
(update-in [:y] div-len))))))
(defn direction-vector
"(Normalized) direction vector from point A to point B"
[point-from point-to]
(normalize (merge-with - point-to point-from)))
(defn weight-point "Calculates the weight point of the points"
[& points]
(let [n (count points)
sums (apply v+ points)]
(-> sums
(update-in [:x] #(/ % n))
(update-in [:y] #(/ % n)))))
;;============== TESTS ==================
(defn is= [a b]
(test/is (= a b)))
(defn is-close-enough [vec-1 vec-2]
(test/is (> 0.001
(magnitude
(merge-with - vec-1 vec-2)))))
(test/deftest some-tests
(is= 5.0 (magnitude {:x 4 :y 3}))
(is= 5.0 (distance {:x 1 :y 3} {:x 4 :y 7}))
(is= {:x 1.0 :y 0.0} (normalize {:x 4 :y 0}))
(is= {:x 0.0 :y 1.0} (normalize {:x 0 :y 6}))
(is-close-enough {:x (Math/sqrt 0.5) :y (Math/sqrt 0.5)} (normalize {:x 8 :y 8}))
(is= {:x 0.0 :y 1.0} (direction-vector {:x 2 :y 6} {:x 2 :y 8}))
(is= {:x -1.0 :y 0.0} (direction-vector {:x 8 :y 6} {:x 5 :y 6}))
(is= {:x 15 :y 20} (v+ {:x 4 :y 6} {:x 6 :y 10} {:x 5 :y 4})))
(test/deftest cartesian->polar-test
(is= {:angle 0.0, :magnitude 1.0} (cartesian->polar {:x 1 :y 0}))
(is= {:angle Math/PI, :magnitude 1.0} (cartesian->polar {:x -1 :y 0}))
(is-close-enough {:x 3 :y 7} (-> {:x 3 :y 7} cartesian->polar polar->cartesian)))
(test/deftest rotate-test
(is-close-enough {:x 0 :y 1} (rotate-cartesian {:x 1 :y 0} (/ Math/PI 2)))
(is-close-enough {:x -1 :y 0} (rotate-cartesian {:x 1 :y 0} Math/PI))
(is-close-enough {:x 0 :y -1} (rotate-cartesian {:x -1 :y 0} (/ Math/PI 2)))
(is-close-enough {:x 1 :y 0} (rotate-cartesian {:x -1 :y 0} Math/PI))
)
(test/deftest weight-point-test
(is-close-enough {:x 4 :y 6} (weight-point {:x 1 :y 4} {:x 7 :y 8})))
(test/run-tests 'clojure-study.ideas.swarm.vector-algebra)

No comments :

Post a Comment