September 2017 Update

This commit is contained in:
Ingy döt Net 2017-09-23 10:01:46 +02:00
parent bba7bfd280
commit ba8067c3b7
14570 changed files with 153136 additions and 63871 deletions

View file

@ -0,0 +1,89 @@
(defun main ()
(let ((dims 0) (target nil) (hits 0))
;;; distance node to target:
;;; returns squared euclidean distance, or squared semi distance if option set
(defun distance (n &optional (semi nil))
(if semi (expt (- (nth (first n) (second n)) (nth (first n) target)) 2)
(reduce #'+ (mapcar (lambda (x y) (* (- x y) (- x y))) (second n) target))))
;;; returns true if target is to its left in axis dim
(defun target< (n)
(< (nth (first n) target) (nth (first n) (second n))))
;;; return the next child when nn searching, return opposing child if option oppose set
(defun next-node (n &optional (oppose nil))
(if (or (and (target< n) (not oppose)) (and (not (target< n)) oppose)) (third n) (fourth n)))
;;; a kdtree is a binary tree where nodes are:
;;; terminal: (axis data-point), or
;;; branch: (axis split-point (left-kdtree) (right-kdtree))
(defun make-kdtree(axis data)
(if (null data) nil
(if (eql (length data) 1) ; singleton?
(list axis (first data)) ;; terminal node
;; else branch node:
;; #pts=odd splits list into 2 even parts with sp in middle
;; #pts=even splits list into 2 uneven parts with shorter length first (but never nil)
(let ((sd (sort (copy-list data) #'< :key (lambda (x) (nth axis x)))) ;; sort the axis ordinates
(sp (truncate (/ (length data) 2))) ;; get mid pt
(nxta (mod (1+ axis) dims)))
(list axis (nth sp sd) (make-kdtree nxta (subseq sd 0 sp)) (make-kdtree nxta (subseq sd (1+ sp))))))))
;;; depth first visit all nodes in kdtree and optionally apply a function to each node visited
(defun visit-kdtree (kdt &key (node-function null))
(when kdt
(when node-function (funcall node-function kdt))
(visit-kdtree (third kdt) :node-function node-function)
(visit-kdtree (fourth kdt) :node-function node-function)))
;;; count of the terminal nodes
(defun count-nodes (kdt)
(if kdt
(if (eql (length kdt) 2) 1
(+ 1 (count-nodes (third kdt)) (count-nodes (fourth kdt))))
0))
;;; nearest neighbour search
(defun nn-kdtree (kdt node-stack)
(when kdt
;; stage 1 - find the 'closest' terminal node using insertion logic
(let ((best (do ((node kdt (next-node node))) ((not (next-node node)) (incf hits) node) ;; return first best est.
(push node node-stack) (incf hits)))) ; iteration
;; stage 2 - unwind the path, at each node if node is closer then make it best
(do ((node (pop node-stack) (pop node-stack))) ((null node) best) ;; return nearest pt
;; iteration: update best if node is closer
(when (< (distance node) (distance best))
(setf best node))
;; venture down opposing side if split point is inside HS
(let ((opposing-best
(if (< (distance node 'semi) (distance best)) ; use semi dist here
(nn-kdtree (next-node node 'opposite) (list))
nil))) ;; otherwise ignore this subtree
(when (and opposing-best (< (distance opposing-best) (distance best)))
(setf best opposing-best)))))))
;;; process one set of data & optionally display tree
(defun process (data tgt &optional (render nil))
(setf target tgt)
(setf dims (length target))
(setf hits 0)
(let* ((kdt (make-kdtree 0 data)) (nn (nn-kdtree kdt (list))))
(when render
(visit-kdtree kdt
:node-function (lambda (n)
(format t "~A node: axis:~A point: ~A target:~A semi-distance-sqd:~A euclidean-distance-sqd:~A~%"
(if (not (next-node n)) "TERMINAL" "BRANCH") (first n) (second n)
target (distance n 'semi) (distance n)))))
(format t "~%NN to ~A is ~A, distance ~A [tree has ~A nodes, ~A were visited.]~%" target (second nn) (sqrt (distance nn)) (count-nodes kdt) hits)))
;; MAIN: TASK 1 - nn search small set of 2D points
(process '((2 3) (5 4) (9 6) (4 7) (8 1) (7 2)) '(9 2) 'render)
;; TASK 2 - nn search 1000 coordinate points in 3D space
(process
(progn (let ((ll (list))) (dotimes (i 10) (dotimes (j 10) (dotimes (k 10) (push (list i j k) ll)))) ll))
(list (float (/ (random 1000) 100)) (float (/ (random 1000) 100)) (float (/ (random 1000) 100))))))

View file

@ -26,7 +26,7 @@ class Kd_tree(n, bounds) {
}
var s2 = ((split + 1) % d.len); # cycle coordinates
Kd_node(d: d, split :split,
Kd_node(d: d, split: split,
left: self.nk2(s2, exset.first(m)),
right: self.nk2(s2, exset.last(m-1)));
}
@ -96,12 +96,12 @@ func find_nearest(k, t, p) {
}
func show_nearest(k, heading, kd, p) {
print <<"-END"
print <<-"END"
#{heading}:
Point: [#{p.join(',')}]
END
var n = find_nearest(k, kd, p);
print <<"-END"
print <<-"END"
Nearest neighbor: [#{n.nearest.join(',')}]
Distance: #{sqrt(n.dist_sqd)}
Nodes visited: #{n.nodes_visited()}

View file

@ -0,0 +1,63 @@
class KdTree{
const NEAREST=0, DIST_SQD=1, NODES_VISITED=2;
class KdNode{
fcn init(_dom_elt,_split,_left,_right){
var dom_elt=_dom_elt.apply("toFloat"),
split=_split, left=_left, right=_right;
}
}
fcn init(pts,_bounds){ // pts is points is ( (x,y,..),..)
var n=fcn(split, exset){
if(not exset) return(Void);
exset=exset.copy() // mutable lists sort much quicker than read only
.sort('wrap(abc,xyz){ abc[split]<xyz[split] });
m,d:=exset.len()/2, exset[m];
while(m+1<exset.len() and exset[m + 1][split]==d[split]){ m+=1 }
s2:=(split+1)%d.len(); # cycle coordinates
KdNode(d,split,self.fcn(s2,exset[0,m]),
self.fcn(s2,exset[m+1,*]))
}(0,pts);
var bounds=_bounds;
}
fcn findNearest(k,p){
fcn(node,target,hr,max_dist_sqd,k){
if(not node) return(k.pump(List,0.0), (0.0).MAX, 0); // fake node far away
nodes_visited,s,pivot:=1, node.split, node.dom_elt;
left_hr,right_hr:=hr.copy(True), hr.copy(True); // deep-ish copy
left_hr.max[s]=right_hr.min[s]=pivot[s];
reg nearer_node, nearer_hr, further_node, further_hr;
if(target[s]<=pivot[s]){
nearer_node, nearer_hr = node.left, left_hr;
further_node, further_hr = node.right, right_hr;
}else{
nearer_node, nearer_hr = node.right, right_hr;
further_node, further_hr = node.left, left_hr;
}
n1:=self.fcn(nearer_node, target, nearer_hr, max_dist_sqd, k);
nearest,dist_sqd:=n1; // n1 is ( (a,b),distance,#visited )
nodes_visited+=n1[NODES_VISITED];
if(dist_sqd<max_dist_sqd) max_dist_sqd=dist_sqd;
d:=(pivot[s] - target[s]).pow(2);
if(d>max_dist_sqd) return(nearest, dist_sqd, nodes_visited);
d:=sqd(pivot,target);
if(d<dist_sqd) nearest,dist_sqd,max_dist_sqd=pivot,d,dist_sqd;
n2:=self.fcn(further_node, target, further_hr, max_dist_sqd,k);
nodes_visited+=n2[NODES_VISITED];
if(n2[DIST_SQD]<dist_sqd) nearest,dist_sqd=n2;
return(nearest, dist_sqd, nodes_visited)
}(n, p, bounds, (0.0).MAX,k)
}
fcn [private] sqd(p1,p2){ // point deltas squared and summed
p1.zipWith(fcn(a,b){ (a-b).pow(2) }, p2).sum(0.0)
}
fcn show_nearest(k, heading, p){
println(heading,":");
println("Point: ",p);
n:=findNearest(k,p);
println("Nearest neighbor:", n[NEAREST]);
println("Distance: ", n[DIST_SQD].sqrt());
println("Nodes visited: ", n[NODES_VISITED], "\n");
}
}

View file

@ -0,0 +1,5 @@
class Orthotope{ fcn init(mi,ma){ var min=mi,max=ma; } }
kd1:=KdTree(T(T(2,3), T(5,4), T(9,6), T(4,7), T(8,1), T(7,2)),
Orthotope(List(0,0), List(10,10)));
kd1.show_nearest(2, "Wikipedia example data", T(9,2));

View file

@ -0,0 +1,9 @@
fcn randomPoint(k){ k.pump(List,(0.0).random(1)) } // ( (p1,p2,,pk), ... n of them), p in [0..1]
fcn randomPoints(k,n){ // ( (p1,p2,,pk), ... n of them), p in [0..1]
n.pump(List,randomPoint.fp(k))
}
N:=40000;
kd2:=KdTree(randomPoints(3,N), Orthotope(L(0,0,0), L(1,1,1)));
kd2.show_nearest(2, String("k-d tree with ", N," random 3D points"),
randomPoint(3));