### Author Topic: Kd Tree  (Read 9158 times)

0 Members and 1 Guest are viewing this topic.

#### gile

• Water Moccasin
• Posts: 2182
• Marseille, France
##### Re: Kd Tree
« Reply #15 on: April 03, 2013, 06:05:14 PM »
thanks again Tony, I get some improvement using your QuickSelectMedian<T>() method.

Here were I am:

Benchmarck (Release mode for 100K points)
Code: [Select]
`Connect using Parallel = FalseGet points:      236Build tree:      203Connect points:  265Draw lines:      722Total:          1426Connect using Parallel = TrueGet points:      244Build tree:      105Connect points:  282Draw lines:      721Total:          1352`
The Point3dTree class.
I added a NearestNeighbour() method to find the nearest point of a given point.
Code - C#: [Select]
`using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.Linq;using Autodesk.AutoCAD.Geometry; namespace PointKdTree{    public class KdTreeNode<T>    {        public KdTreeNode(T value) { this.Value = value; }         public T Value { get; internal set; }        public KdTreeNode<T> LeftChild { get; internal set; }        public KdTreeNode<T> RightChild { get; internal set; }        public int Depth { get; internal set; }        public bool Processed { get; set; }    }     public class Point3dTree    {        private int dimension;        internal static bool useParallel = false;        public KdTreeNode<Point3d> Root { get; private set; }         public Point3dTree(IEnumerable<Point3d> points) : this(points, false) { }         public Point3dTree(IEnumerable<Point3d> points, bool ignoreZ)        {            if (points == null)                throw new ArgumentNullException("points");            this.dimension = ignoreZ ? 2 : 3;             Point3d[] pts = points.Distinct().ToArray();            this.Root = Construct(pts, 0);        }         public List<Point3d> NearestNeighbours(KdTreeNode<Point3d> node, double radius)        {            if (node == null)                throw new ArgumentNullException("node");            List<Point3d> result = new List<Point3d>();            NearestNeighbours(node.Value, radius, this.Root.LeftChild, result);            NearestNeighbours(node.Value, radius, this.Root.RightChild, result);            return result;        }         private void NearestNeighbours(Point3d center, double radius, KdTreeNode<Point3d> node, List<Point3d> result)        {            if (node == null) return;            Point3d pt = node.Value;            if (!node.Processed && center.DistanceTo(pt) <= radius)            {                result.Add(pt);            }            int d = node.Depth % this.dimension;            double coordCen = center[d];            double coordPt = pt[d];            if (Math.Abs(coordCen - coordPt) > radius)            {                if (coordCen < coordPt)                {                    NearestNeighbours(center, radius, node.LeftChild, result);                }                else                {                    NearestNeighbours(center, radius, node.RightChild, result);                }            }            else            {                NearestNeighbours(center, radius, node.LeftChild, result);                NearestNeighbours(center, radius, node.RightChild, result);            }        }         public Point3d NearestNeighbour(Point3d location)        {            return NearestNeighbour(location, this.Root, this.Root.Value, double.MaxValue);        }         private Point3d NearestNeighbour(Point3d location, KdTreeNode<Point3d> node, Point3d currentBest, double bestDistance)        {            if (node == null)                return currentBest;            int dim = node.Depth % this.dimension;            Point3d nodeLocation = node.Value;            double distance = location.DistanceTo(nodeLocation);            if (distance >= 0.0 && distance < bestDistance)            {                currentBest = nodeLocation;                bestDistance = distance;            }            bool isLeftNearer = location[dim] < nodeLocation[dim];            KdTreeNode<Point3d> nearestChild = isLeftNearer ? node.LeftChild : node.RightChild;            if (nearestChild != null)            {                currentBest = NearestNeighbour(location, nearestChild, currentBest, bestDistance);                bestDistance = currentBest.DistanceTo(location);            }            if (bestDistance > Math.Abs(location[dim] - nodeLocation[dim]))            {                KdTreeNode<Point3d> farestChild = isLeftNearer ? node.RightChild : node.LeftChild;                if (farestChild != null)                {                    currentBest = NearestNeighbour(location, farestChild, currentBest, bestDistance);                    bestDistance = currentBest.DistanceTo(location);                }            }            return currentBest;        }         private KdTreeNode<Point3d> Construct(Point3d[] points, int depth)        {            int length = points.Length;            if (length == 0) return null;            int d = depth % this.dimension;            Point3d median = QuickSelectMedian(points, (p1, p2) => p1[d].CompareTo(p2[d]));            KdTreeNode<Point3d> node = new KdTreeNode<Point3d>(median);            node.Depth = depth;            int mid = length / 2;            int rlen = length - mid - 1;            Point3d[] left = new Point3d[mid];            Point3d[] right = new Point3d[rlen];            Array.Copy(points, 0, left, 0, mid);            Array.Copy(points, mid + 1, right, 0, rlen);            if (useParallel && depth < 4)            {                System.Threading.Tasks.Parallel.Invoke(                   () => node.LeftChild = Construct(left, depth + 1),                   () => node.RightChild = Construct(right, depth + 1)                );            }            else            {                node.LeftChild = Construct(left, depth + 1);                node.RightChild = Construct(right, depth + 1);            }            return node;        }         // From Tony Tanzillo         // http://www.theswamp.org/index.php?topic=44312.msg495808#msg495808        private T QuickSelectMedian<T>(T[] items, Comparison<T> compare)        {            int l = items.Length;            int k = l / 2;            if (items == null || items.Length == 0)                throw new ArgumentException("array");            int from = 0;            int to = l - 1;            while (from < to)            {                int r = from;                int w = to;                T current = items[(r + w) / 2];                while (r < w)                {                    if (compare(items[r], current) > -1)                    {                        var tmp = items[w];                        items[w] = items[r];                        items[r] = tmp;                        w--;                    }                    else                    {                        r++;                    }                }                if (compare(items[r], current) > 0)                {                    r--;                }                if (k <= r)                {                    to = r;                }                else                {                    from = r + 1;                }            }            return items[k];        }    }} `

The command used for the tests
Code - C#: [Select]
`// (C) Copyright 2012 by Gilles Chanteau //using System.Collections.Generic;using System.Collections.Concurrent;using System.Linq;using Autodesk.AutoCAD.ApplicationServices;using Autodesk.AutoCAD.DatabaseServices;using Autodesk.AutoCAD.EditorInput;using Autodesk.AutoCAD.Geometry;using Autodesk.AutoCAD.Runtime;using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;using System; [assembly: CommandClass(typeof(PointKdTree.CommandMethods))] namespace PointKdTree{    public class CommandMethods    {        struct Point3dPair        {            public readonly Point3d Start;            public readonly Point3d End;             public Point3dPair(Point3d start, Point3d end)            {                this.Start = start;                this.End = end;            }        }         // From Tony Tanzillo        [CommandMethod("CONNECT_PARALLEL")]        public static void ConnectParallelSwitch()        {            Point3dTree.useParallel ^= true;            Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(               "\nConnect using Parallel = {0}", Point3dTree.useParallel);        }         [CommandMethod("CONNECT")]        public void Connect()        {            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();            sw.Start();            Document doc = AcAp.DocumentManager.MdiActiveDocument;            Database db = doc.Database;            Editor ed = doc.Editor;            RXClass rxc = RXClass.GetClass(typeof(DBPoint));            using (BlockTableRecord btr = (BlockTableRecord)db.CurrentSpaceId.Open(OpenMode.ForWrite))            {                long t0 = sw.ElapsedMilliseconds;                ObjectId[] ids = btr.Cast<ObjectId>().Where(id => id.ObjectClass == rxc).ToArray();                int len = ids.Length;                Point3d[] pts = new Point3d[len];                for (int i = 0; i < len; i++)                {                    using (DBPoint pt = (DBPoint)ids[i].Open(OpenMode.ForRead))                    {                        pts[i] = pt.Position;                    }                }                long t1 = sw.ElapsedMilliseconds;                Point3dTree tree = new Point3dTree(pts, true);                long t2 = sw.ElapsedMilliseconds;                List<Point3dPair> pairs = new List<Point3dPair>();                ConnectPoints(tree, tree.Root, 70.0, pairs);                long t3 = sw.ElapsedMilliseconds;                foreach (Point3dPair pair in pairs)                {                    using (Line line = new Line(pair.Start, pair.End))                    {                        btr.AppendEntity(line);                    }                }                sw.Stop();                long t4 = sw.ElapsedMilliseconds;                ed.WriteMessage("\nConnect using Parallel = {0}", Point3dTree.useParallel);                ed.WriteMessage(                    "\nGet points:{0,9}\nBuild tree:{1,9}\nConnect points:{2,5}\nDraw lines:{3,9}\nTotal:{4,14}",                    t1 - t0, t2 - t1, t3 - t2, t4 - t3, t4);            }        }         private void ConnectPoints(Point3dTree tree, KdTreeNode<Point3d> node, double dist, List<Point3dPair> pointPairs)        {            if (node == null) return;            node.Processed = true;            Point3d center = node.Value;            foreach (Point3d pt in tree.NearestNeighbours(node, dist))            {                pointPairs.Add(new Point3dPair(center, pt));            }            ConnectPoints(tree, node.LeftChild, dist, pointPairs);            ConnectPoints(tree, node.RightChild, dist, pointPairs);        }    }} `
Speaking English as a French Frog

#### TheMaster

• Guest
##### Re: Kd Tree
« Reply #16 on: April 03, 2013, 07:42:37 PM »
Hi Gile - Great job.

Unless you're running on a box with 8 physical CPU cores, change the test to run in parallel only if depth < 3 for a quad-core system. You could also check the number of physical CPU cores at runtime and adjust that (for example, on a 2-core system, use Parallel.Invoke() only if depth < 2).

#### TheMaster

• Guest
##### Re: Kd Tree
« Reply #17 on: April 07, 2013, 11:25:11 AM »
Hi Gile - Great job.

Unless you're running on a box with 8 physical CPU cores, change the test to run in parallel only if depth < 3 for a quad-core system. You could also check the number of physical CPU cores at runtime and adjust that (for example, on a 2-core system, use Parallel.Invoke() only if depth < 2).

As it turns out, getting the number of physical cores isn't trivial.

Code - Text: [Select]
` /// <summary>/// Queries the system for the number of physical/// CPU cores and logical processors. Requires a/// reference to System.Management.dll/// /// Credit: http://www.stev.org/post/2011/10/27/C-Number-Of-Cores-and-Processors.aspx/// /// </summary> public static class ThreadSystemInfo{    static ThreadSystemInfo()    {        try        {            ObjectQuery wql = new ObjectQuery( "SELECT * FROM Win32_Processor" );            using( var searcher = new ManagementObjectSearcher( wql ) )            using( var results = searcher.Get() )            {                if( results != null )                {                    var items = results.Cast<ManagementObject>();                    if( items.Any() )                    {                        var item = items.First();                        cores = int.Parse( item["NumberOfCores"].ToString() );                        logicalProcessors = int.Parse( item["NumberOfLogicalProcessors"].ToString() );                    }                }            }        }        catch // above can fail on some systems, so kludge it.        {            logicalProcessors = System.Environment.ProcessorCount;            if( logicalProcessors > 1 )                cores = logicalProcessors / 2;        }    }     private static int cores = 1;    public static int PhysicalCoreCount    {        get        {            return cores;        }    }     private static int logicalProcessors = 1;    public static int LogicalProcessorCount    {        get        {            return logicalProcessors;        }    }}  `
« Last Edit: April 07, 2013, 11:45:21 AM by TT »

#### gile

• Water Moccasin
• Posts: 2182
• Marseille, France
##### Re: Kd Tree
« Reply #18 on: April 08, 2013, 04:19:01 AM »
Quote
Hi Gile - Great job.
"With a Little Help from My Friends", thanks.

For now I care less which is specific to "connect points challenge" and I focus more on the implementation of a class with methods more useful.
Speaking English as a French Frog

#### TheMaster

• Guest
##### Re: Kd Tree
« Reply #19 on: April 08, 2013, 07:15:20 AM »
Quote
Hi Gile - Great job.
"With a Little Help from My Friends", thanks.

For now I care less which is specific to "connect points challenge" and I focus more on the implementation of a class with methods more useful.

In case anyone wants to know more about the concepts of parallelism and its use in recursive solutions to various programming problems, like that which was used in this thread, this is highly-recommended reading:

http://blogs.msdn.com/b/pfxteam/archive/2008/01/31/7357135.aspx

#### Jeff H

• Needs a day job
• Posts: 6012
##### Re: Kd Tree
« Reply #20 on: April 09, 2013, 05:55:51 PM »
Hi Gile,
I am coming down with something and hopefully will not get too sick, but if you like I could send you more info tomorrow or the next day when feeling better if you wanted analyze it.

#### gile

• Water Moccasin
• Posts: 2182
• Marseille, France
##### Re: Kd Tree
« Reply #21 on: April 12, 2013, 12:09:18 PM »
Thanks Jeff, I hope you feel better.

I added some public methods to the Point3dTree class.

This class may be used to improve performances in case of many queries in a quite large amount of points.
It targets the Framwork 4.0 to use some parallelization features.

According to the value of the 'ignoreZ' constructor argument, a 2d tree (ignoreZ = true) or a 3d tree (ignoreZ = false, default).
Use ignoreZ = true if all points in the input collection lie on a plane parallel to XY or if the points have to be considered as projected on the XY plane.

Public methods:
NearestNeighbour(Point3d) Gets the nearest neighbour.
NearestNeighbours(Point3d, int) Gets the n nearest neighbours.
NearestNeighbours(Point3d, double) Gets the nearest neighbours within the distance.
BoxedRange(Point3d, Point3d) Gets the points in a range.
ConnectAll(double) Gets all the pairs of points which distance is less or equal than the specified distance.
The last one was was almost used for performance tests (and to reply to this challenge).

Code - C#: [Select]
`using System;using System.Collections.Generic;using System.Linq;using Autodesk.AutoCAD.Geometry; namespace PointKdTree{    /// <summary>    /// Node of a Point3dTree    /// </summary>    public class TreeNode    {        /// <summary>        /// Creates a new instance of TreeNode        /// </summary>        /// <param name="value">The 3d point value of the node.</param>        public TreeNode(Point3d value) { this.Value = value; }         /// <summary>        /// Gets the value (Point3d) of the node.        /// </summary>        public Point3d Value { get; internal set; }         /// <summary>        /// Gets the parent node.        /// </summary>        public TreeNode Parent { get; internal set; }         /// <summary>        /// Gets the left child node.        /// </summary>        public TreeNode LeftChild { get; internal set; }         /// <summary>        /// Gets the right child node.        /// </summary>        public TreeNode RightChild { get; internal set; }         /// <summary>        /// Gets the depth of the node in tree.        /// </summary>        public int Depth { get; internal set; }    }     /// <summary>    /// Provides methods to organize 3d points in a Kd tree structure to speed up the search of neighbours.    /// A boolean constructor parameter (ignoreZ) indicates if the resulting Kd tree is a 3d tree or a 2d tree.    /// Use ignoreZ = true if all points in the input collection lie on a plane parallel to XY     /// or if the points have to be considered as projected on the XY plane.     /// </summary>    public class Point3dTree    {        #region Private fields         private int dimension;        private int parallelDepth;        private bool ignoreZ;        private Point2d center2d;        private Point3d center;        private Point3d current;        private Func<Point3d, double> distanceTo;         #endregion         #region Constructor         /// <summary>        /// Creates an new instance of Point3dTree.        /// </summary>        /// <param name="points">The Point3d collection to fill the tree.</param>        /// <param name="ignoreZ">A value indicating if the Z coordinate of points is ignored         /// (as if all points were projected to the XY plane).</param>        public Point3dTree(IEnumerable<Point3d> points, bool ignoreZ = false)        {            if (points == null)                throw new ArgumentNullException("points");            this.ignoreZ = ignoreZ;            this.dimension = ignoreZ ? 2 : 3;            int numProc = System.Environment.ProcessorCount;            this.parallelDepth = -1;            while (numProc >> ++this.parallelDepth > 1) ;            Point3d[] pts = points.Distinct().ToArray();            this.Root = Construct(pts, 0, null);        }         #endregion         #region Public properties         /// <summary>        /// Gets the root node of the tree.        /// </summary>        public TreeNode Root { get; private set; }         #endregion         #region Public methods         /// <summary>        /// Gets the nearest neighbour.        /// </summary>        /// <param name="point">The point from which search the nearest neighbour.</param>        /// <returns>The nearest point in the collection from the specified one.</returns>        public Point3d NearestNeighbour(Point3d point)        {            this.center = point;            this.current = this.Root.Value;            if (ignoreZ)            {                center2d = point.Flatten();                this.distanceTo = Distance2dTo;            }            else            {                this.distanceTo = Distance3dTo;            }            return GetNeighbour(this.Root, this.Root.Value, double.MaxValue);        }         /// <summary>        /// Gets the neighbours within the specified distance.        /// </summary>        /// <param name="point">The point from which search the nearest neighbours.</param>        /// <param name="radius">The distance in which collect the neighbours.</param>        /// <returns>The points which distance from the specified point is less or equal to the specified distance.</returns>        public Point3dCollection NearestNeighbours(Point3d point, double radius)        {            Point3dCollection points = new Point3dCollection();            this.center = point;            if (this.ignoreZ)            {                this.center2d = point.Flatten();                this.distanceTo = Distance2dTo;            }            else            {                this.distanceTo = Distance3dTo;            }            GetNeighboursAtDistance(radius, this.Root.LeftChild, points);            GetNeighboursAtDistance(radius, this.Root.RightChild, points);            return points;        }         /// <summary>        /// Gets the n nearest neighbours.        /// </summary>        /// <param name="point">The point from which search the nearest neighbours.</param>        /// <param name="number">The number of points to collect.</param>        /// <returns>The n nearest neighbours of the specified point.</returns>        public Point3dCollection NearestNeighbours(Point3d point, int number)        {            List<Tuple<double, Point3d>> pairs = new List<Tuple<double, Point3d>>(number);            this.center = point;            if (ignoreZ)            {                this.center2d = point.Flatten();                this.distanceTo = Distance2dTo;            }            else            {                this.distanceTo = Distance3dTo;            }            GetNNeighbours(number, double.MaxValue, this.Root, pairs);            Point3dCollection points = new Point3dCollection();            for (int i = 0; i < pairs.Count; i++)            {                points.Add(pairs[i].Item2);            }            return points;        }         /// <summary>        /// Gets the points in a range.        /// </summary>        /// <param name="pt1">The first corner of range.</param>        /// <param name="pt2">The opposite corner of the range.</param>        /// <returns>All points within the box.</returns>        public Point3dCollection BoxedRange(Point3d pt1, Point3d pt2)        {            Point3d lowerLeft = new Point3d(                Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y), Math.Min(pt1.Z, pt2.Z));            Point3d upperRight = new Point3d(                Math.Max(pt1.X, pt2.X), Math.Max(pt1.Y, pt2.Y), Math.Max(pt1.Z, pt2.Z));            Point3dCollection points = new Point3dCollection();            FindRange(lowerLeft, upperRight, this.Root, points);            return points;        }         /// <summary>        /// Gets all the pairs of points which distance is less or equal than the specified distance.        /// </summary>        /// <param name="radius">The maximum distance between two points. </param>        /// <returns>The pairs of points which distance is less or equal than the specified distance.</returns>        public List<Tuple<Point3d, Point3d>> ConnectAll(double radius)        {            List<Tuple<Point3d, Point3d>> connexions = new List<Tuple<Point3d, Point3d>>();            Stack<TreeNode> nodes = new Stack<TreeNode>();            GetConnexions(this.Root, radius, connexions, nodes);            return connexions;        }         #endregion         #region Private methods         private TreeNode Construct(Point3d[] points, int depth, TreeNode parent)        {            int length = points.Length;            if (length == 0) return null;            int d = depth % this.dimension;            Point3d median = points.QuickSelectMedian((p1, p2) => p1[d].CompareTo(p2[d]));            TreeNode node = new TreeNode(median);            node.Depth = depth;            node.Parent = parent;            int mid = length / 2;            int rlen = length - mid - 1;            Point3d[] left = new Point3d[mid];            Point3d[] right = new Point3d[rlen];            Array.Copy(points, 0, left, 0, mid);            Array.Copy(points, mid + 1, right, 0, rlen);            if (depth < this.parallelDepth)            {                System.Threading.Tasks.Parallel.Invoke(                   () => node.LeftChild = Construct(left, depth + 1, node),                   () => node.RightChild = Construct(right, depth + 1, node)                );            }            else            {                node.LeftChild = Construct(left, depth + 1, node);                node.RightChild = Construct(right, depth + 1, node);            }            return node;        }         private Point3d GetNeighbour(TreeNode node, Point3d currentBest, double bestDist)        {            if (node == null)                return currentBest;            this.current = node.Value;            int d = node.Depth % this.dimension;            double coordCen = center[d];            double coordCur = current[d];            double dist = this.distanceTo(this.current);            if (dist >= 0.0 && dist < bestDist)            {                currentBest = this.current;                bestDist = dist;            }            if (bestDist >= Math.Abs(coordCen - coordCur))            {                currentBest = GetNeighbour(                    coordCen < coordCur ? node.LeftChild : node.RightChild, currentBest, bestDist);                bestDist = this.distanceTo(currentBest);            }            else            {                currentBest = GetNeighbour(node.LeftChild, currentBest, bestDist);                bestDist = this.distanceTo(currentBest);                currentBest = GetNeighbour(node.RightChild, currentBest, bestDist);                bestDist = this.distanceTo(currentBest);            }            return currentBest;        }         private void GetNeighboursAtDistance(double radius, TreeNode node, Point3dCollection points)        {            if (node == null) return;            this.current = node.Value;            double dist = this.distanceTo(this.current);            if (dist <= radius)            {                points.Add(this.current);            }            int d = node.Depth % this.dimension;            double coordCen = this.center[d];            double coordCur = this.current[d];            if (Math.Abs(coordCen - coordCur) > radius)            {                if (coordCen < coordCur)                {                    GetNeighboursAtDistance(radius, node.LeftChild, points);                }                else                {                    GetNeighboursAtDistance(radius, node.RightChild, points);                }            }            else            {                GetNeighboursAtDistance(radius, node.LeftChild, points);                GetNeighboursAtDistance(radius, node.RightChild, points);            }        }         private void GetNNeighbours(int number, double worstDist, TreeNode node, List<Tuple<double, Point3d>> pairs)        {            if (node == null) return;            this.current = node.Value;            double dist = this.distanceTo(this.current);            int cnt = pairs.Count;            if (cnt < number)            {                pairs.Add(new Tuple<double, Point3d>(dist, this.current));                pairs.Sort((p1, p2) => p1.Item1.CompareTo(p2.Item1));                worstDist = pairs[cnt].Item1;            }            else if (dist < worstDist)            {                pairs.RemoveAt(number - 1);                pairs.Add(new Tuple<double, Point3d>(dist, this.current));                pairs.Sort((p1, p2) => p1.Item1.CompareTo(p2.Item1));                worstDist = pairs[number - 1].Item1;            }            int d = node.Depth % this.dimension;            double coordCen = center[d];            double coordCur = current[d];            if (Math.Abs(coordCen - coordCur) > worstDist)            {                if (coordCen < coordCur)                {                    GetNNeighbours(number, worstDist, node.LeftChild, pairs);                }                else                {                    GetNNeighbours(number, worstDist, node.RightChild, pairs);                }            }            else            {                GetNNeighbours(number, worstDist, node.LeftChild, pairs);                GetNNeighbours(number, pairs[pairs.Count - 1].Item1, node.RightChild, pairs);            }        }         private void FindRange(Point3d lowerLeft, Point3d upperRight, TreeNode node, Point3dCollection points)        {            if (node == null)                return;            this.current = node.Value;            if (ignoreZ)            {                if (this.current.X >= lowerLeft.X && this.current.X <= upperRight.X &&                    this.current.Y >= lowerLeft.Y && this.current.Y <= upperRight.Y)                    points.Add(this.current);            }            else            {                if (this.current.X >= lowerLeft.X && this.current.X <= upperRight.X &&                    this.current.Y >= lowerLeft.Y && this.current.Y <= upperRight.Y &&                    this.current.Z >= lowerLeft.Z && this.current.Z <= upperRight.Z)                    points.Add(this.current);            }            int d = node.Depth % this.dimension;            if (upperRight[d] < this.current[d])                FindRange(lowerLeft, upperRight, node.LeftChild, points);            else if (lowerLeft[d] > this.current[d])                FindRange(lowerLeft, upperRight, node.RightChild, points);            else            {                FindRange(lowerLeft, upperRight, node.LeftChild, points);                FindRange(lowerLeft, upperRight, node.RightChild, points);            }        }         private void GetConnexions(TreeNode node, double radius, List<Tuple<Point3d, Point3d>> connexions, Stack<TreeNode> nodes)        {            if (node == null) return;            Point3dCollection points = new Point3dCollection();            this.center = node.Value;            if (ignoreZ)            {                this.center2d = this.center.Flatten();                this.distanceTo = Distance2dTo;            }            else            {                this.distanceTo = Distance3dTo;            }            foreach (TreeNode tn in nodes)            {                TreeNode parent = tn.Parent;                int d = parent.Depth % this.dimension;                if (Math.Abs(this.center[d] - parent.Value[d]) <= radius)                {                    GetNeighboursAtDistance(radius, tn, points);                }            }            GetNeighboursAtDistance(radius, node.LeftChild, points);            GetNeighboursAtDistance(radius, node.RightChild, points);            for (int i = 0; i < points.Count; i++)            {                connexions.Add(new Tuple<Point3d, Point3d>(this.center, points[i]));            }             if (node.RightChild != null)            {                nodes.Push(node.RightChild);            }            else if (node.LeftChild == null && nodes.Count > 0)            {                nodes.Pop();            }            GetConnexions(node.LeftChild, radius, connexions, nodes);            GetConnexions(node.RightChild, radius, connexions, nodes);        }         private double Distance2dTo(Point3d pt)        {            return this.center2d.GetDistanceTo(pt.Flatten());        }         private double Distance3dTo(Point3d pt)        {            return this.center.DistanceTo(pt);        }         #endregion    }     static class Extensions    {        public static Point2d Flatten(this Point3d pt)        {            return new Point2d(pt.X, pt.Y);        }         // Credit: Tony Tanzillo        // http://www.theswamp.org/index.php?topic=44312.msg495808#msg495808        public static T QuickSelectMedian<T>(this T[] items, Comparison<T> compare)        {            int l = items.Length;            int k = l / 2;            if (items == null || l == 0)                throw new ArgumentException("array");            int from = 0;            int to = l - 1;            while (from < to)            {                int r = from;                int w = to;                T current = items[(r + w) / 2];                while (r < w)                {                    if (compare(items[r], current) > -1)                    {                        var tmp = items[w];                        items[w] = items[r];                        items[r] = tmp;                        w--;                    }                    else                    {                        r++;                    }                }                if (compare(items[r], current) > 0)                {                    r--;                }                if (k <= r)                {                    to = r;                }                else                {                    from = r + 1;                }            }            return items[k];        }    }} `
« Last Edit: April 13, 2013, 05:41:57 PM by gile »
Speaking English as a French Frog

#### TheMaster

• Guest
##### Re: Kd Tree
« Reply #22 on: April 12, 2013, 03:09:57 PM »
The last one was was almost used for performance tests (and to reply to this challenge).

I also noticed in one post, that someone is using (getvar "DATE") to measure times - which is not very accurate (for sub-second times).

#### gile

• Water Moccasin
• Posts: 2182
• Marseille, France
##### Re: Kd Tree
« Reply #23 on: April 12, 2013, 04:12:46 PM »
I don't think so, this thread is quite old and all the C/C++ part was/is other my head. I remember there was impressive LISP results from Evgeniy.
Speaking English as a French Frog

#### gile

• Water Moccasin
• Posts: 2182
• Marseille, France
##### Re: Kd Tree
« Reply #24 on: April 13, 2013, 05:32:02 PM »
I changed the way to avoid duplicated lines in the ConnectAll. Rather than a boolean 'Processed' property in the TreeNode, I now use a Stack<TreeNode> containing the  right children of the parents of the current node where to search (or not if far enough) for neighbours. This improves a little the 'connect points" part speed.
The tree is now double linked.

Some results:
Code - Text: [Select]
`100K points (128 026 lines)Get points:      220Build tree:      114Connect points:  199Draw lines:      764Total:          1297 1 Million points (1 280 741 lines)Get points:     2118Build tree:     1388Connect points: 2107Draw lines:    12046Total:         17659`

The testing command:
Code - C#: [Select]
`        [CommandMethod("CONNECT")]        public void Connect()        {            Document doc = AcAp.DocumentManager.MdiActiveDocument;            Database db = doc.Database;            Editor ed = doc.Editor;            try            {                System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();                sw.Start();                RXClass rxc = RXClass.GetClass(typeof(DBPoint));                using (BlockTableRecord btr = (BlockTableRecord)db.CurrentSpaceId.Open(OpenMode.ForWrite))                {                    long t0 = sw.ElapsedMilliseconds;                    ObjectId[] ids = btr.Cast<ObjectId>().Where(id => id.ObjectClass == rxc).ToArray();                    int len = ids.Length;                    Point3d[] pts = new Point3d[len];                    for (int i = 0; i < len; i++)                    {                        using (DBPoint pt = (DBPoint)ids[i].Open(OpenMode.ForRead))                        {                            pts[i] = pt.Position;                        }                    }                    long t1 = sw.ElapsedMilliseconds;                    Point3dTree tree = new Point3dTree(pts, true); // <- true = 2d tree, false = 3d tree                    long t2 = sw.ElapsedMilliseconds;                    List<Tuple<Point3d, Point3d>> pairs = tree.ConnectAll(70.0);                    long t3 = sw.ElapsedMilliseconds;                    foreach (var pair in pairs)                    {                        using (Line line = new Line(pair.Item1, pair.Item2))                        {                            btr.AppendEntity(line);                        }                    }                    db.TransactionManager.QueueForGraphicsFlush();                    sw.Stop();                    long t4 = sw.ElapsedMilliseconds;                    ed.WriteMessage(                        "\nGet points:{0,9}\nBuild tree:{1,9}\nConnect points:{2,5}\nDraw lines:{3,9}\nTotal:{4,14}",                        t1 - t0, t2 - t1, t3 - t2, t4 - t3, t4);                }            }            catch (System.Exception ex)            {                ed.WriteMessage("\n{0}\n{1}", ex.Message, ex.StackTrace);            }        }`
« Last Edit: April 13, 2013, 05:36:49 PM by gile »
Speaking English as a French Frog

#### TheMaster

• Guest
##### Re: Kd Tree
« Reply #25 on: April 14, 2013, 01:25:23 PM »
I don't think so, this thread is quite old and all the C/C++ part was/is other my head. I remember there was impressive LISP results from Evgeniy.

I would expect that once a dataset is indexed, locating points within a given distance should not be too difficult in any language, since the whole point to indexing the data is to minimize the amount of work needed to find nearest coordinates.

LISP is well-suited to tree-based, recursive algorithms, so it wouldn't surprise me that can find points in an index dataset quickly. But, I would be surprised if he was able to construct the tree as fast as it can be done in .NET or native code. I very much doubt that, if for no other reason, because of the inherent overhead of dealing with immutable lists.
« Last Edit: April 14, 2013, 01:34:20 PM by TT »

#### gile

• Water Moccasin
• Posts: 2182
• Marseille, France
##### Re: Kd Tree
« Reply #26 on: April 14, 2013, 01:52:17 PM »
Hi Tony,

It seems to me he didn't use a tree structure but a 'divide and conquer' algorithm as shown by this picture.
By my side, I tried to implement a more reusable (and may be usefull) structure.

I found another way to avoid duplicated connexions with the ConnectAll() method. Rather than storing nodes in a stack, I can get the 'right parents nodes' of the current node using a new property in the TreeNode which indicates if the the node is a left child node or not. This seems to improve a little ConnectAll() speed.

Here's the new implementation.

New version:
- Replaced the using global fields by function parameters so that the public methods can be used in parallel execution.
- Using of square distances

Code - C#: [Select]
`using System;using System.Collections.Generic;using System.Linq;using Autodesk.AutoCAD.Geometry; namespace PointKdTree{    /// <summary>    /// Node of a Point3dTree    /// </summary>    public class TreeNode    {        /// <summary>        /// Creates a new instance of TreeNode        /// </summary>        /// <param name="value">The 3d point value of the node.</param>        public TreeNode(Point3d value) { this.Value = value; }         /// <summary>        /// Gets the value (Point3d) of the node.        /// </summary>        public Point3d Value { get; internal set; }         /// <summary>        /// Gets the parent node.        /// </summary>        public TreeNode Parent { get; internal set; }         /// <summary>        /// Gets the left child node.        /// </summary>        public TreeNode LeftChild { get; internal set; }         /// <summary>        /// Gets the right child node.        /// </summary>        public TreeNode RightChild { get; internal set; }         /// <summary>        /// Gets the depth of the node in tree.        /// </summary>        public int Depth { get; internal set; }         /// <summary>        /// Gets a value indicating if the current node is a LeftChild node.        /// </summary>        public bool IsLeft { get; internal set; }    }     /// <summary>    /// Provides methods to organize 3d points in a Kd tree structure to speed up the search of neighbours.    /// A boolean constructor parameter (ignoreZ) indicates if the resulting Kd tree is a 3d tree or a 2d tree.    /// Use ignoreZ = true if all points in the input collection lie on a plane parallel to XY     /// or if the points have to be considered as projected on the XY plane.     /// </summary>    public class Point3dTree    {        #region Private fields         private int dimension;        private int parallelDepth;        private bool ignoreZ;        private Func<Point3d, Point3d, double> sqrDist;         #endregion         #region Constructor         /// <summary>        /// Creates an new instance of Point3dTree.        /// </summary>        /// <param name="points">The Point3d collection to fill the tree.</param>        /// <param name="ignoreZ">A value indicating if the Z coordinate of points is ignored         /// (as if all points were projected to the XY plane).</param>        public Point3dTree(IEnumerable<Point3d> points, bool ignoreZ = false)        {            if (points == null)                throw new ArgumentNullException("points");            this.ignoreZ = ignoreZ;            this.dimension = ignoreZ ? 2 : 3;            if (ignoreZ)                this.sqrDist = SqrDistance2d;            else                this.sqrDist = SqrDistance3d;            int numProc = System.Environment.ProcessorCount;            this.parallelDepth = -1;            while (numProc >> ++this.parallelDepth > 1) ;            Point3d[] pts = points.Distinct().ToArray();            this.Root = Create(pts, 0, null, false);        }         #endregion         #region Public properties         /// <summary>        /// Gets the root node of the tree.        /// </summary>        public TreeNode Root { get; private set; }         #endregion         #region Public methods         /// <summary>        /// Gets the nearest neighbour.        /// </summary>        /// <param name="point">The point from which search the nearest neighbour.</param>        /// <returns>The nearest point in the collection from the specified one.</returns>        public Point3d NearestNeighbour(Point3d point)        {            return GetNeighbour(point, this.Root, this.Root.Value, double.MaxValue);        }         /// <summary>        /// Gets the neighbours within the specified distance.        /// </summary>        /// <param name="point">The point from which search the nearest neighbours.</param>        /// <param name="radius">The distance in which collect the neighbours.</param>        /// <returns>The points which distance from the specified point is less or equal to the specified distance.</returns>        public Point3dCollection NearestNeighbours(Point3d point, double radius)        {            Point3dCollection points = new Point3dCollection();            GetNeighboursAtDistance(point, radius * radius, this.Root, points);            return points;        }         /// <summary>        /// Gets the given number of nearest neighbours.        /// </summary>        /// <param name="point">The point from which search the nearest neighbours.</param>        /// <param name="number">The number of points to collect.</param>        /// <returns>The n nearest neighbours of the specified point.</returns>        public Point3dCollection NearestNeighbours(Point3d point, int number)        {            List<Tuple<double, Point3d>> pairs = new List<Tuple<double, Point3d>>(number);            GetKNeighbours(point, number, this.Root, pairs);            Point3dCollection points = new Point3dCollection();            for (int i = 0; i < pairs.Count; i++)            {                points.Add(pairs[i].Item2);            }            return points;        }         /// <summary>        /// Gets the points in a range.        /// </summary>        /// <param name="pt1">The first corner of range.</param>        /// <param name="pt2">The opposite corner of the range.</param>        /// <returns>All points within the box.</returns>        public Point3dCollection BoxedRange(Point3d pt1, Point3d pt2)        {            Point3d lowerLeft = new Point3d(                Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y), Math.Min(pt1.Z, pt2.Z));            Point3d upperRight = new Point3d(                Math.Max(pt1.X, pt2.X), Math.Max(pt1.Y, pt2.Y), Math.Max(pt1.Z, pt2.Z));            Point3dCollection points = new Point3dCollection();            FindRange(lowerLeft, upperRight, this.Root, points);            return points;        }         /// <summary>        /// Gets all the pairs of points which distance is less or equal than the specified distance.        /// </summary>        /// <param name="radius">The maximum distance between two points. </param>        /// <returns>The pairs of points which distance is less or equal than the specified distance.</returns>        public List<Tuple<Point3d, Point3d>> ConnectAll(double radius)        {            List<Tuple<Point3d, Point3d>> connexions = new List<Tuple<Point3d, Point3d>>();            GetConnexions(this.Root, radius * radius, connexions);            return connexions;        }         #endregion         #region Private methods         private TreeNode Create(Point3d[] points, int depth, TreeNode parent, bool isLeft)        {            int length = points.Length;            if (length == 0) return null;            int d = depth % this.dimension;            Point3d median = points.QuickSelectMedian((p1, p2) => p1[d].CompareTo(p2[d]));            TreeNode node = new TreeNode(median);            node.Depth = depth;            node.Parent = parent;            node.IsLeft = isLeft;            int mid = length / 2;            int rlen = length - mid - 1;            Point3d[] left = new Point3d[mid];            Point3d[] right = new Point3d[rlen];            Array.Copy(points, 0, left, 0, mid);            Array.Copy(points, mid + 1, right, 0, rlen);            if (depth < this.parallelDepth)            {                System.Threading.Tasks.Parallel.Invoke(                   () => node.LeftChild = Create(left, depth + 1, node, true),                   () => node.RightChild = Create(right, depth + 1, node, false)                );            }            else            {                node.LeftChild = Create(left, depth + 1, node, true);                node.RightChild = Create(right, depth + 1, node, false);            }            return node;        }         private Point3d GetNeighbour(Point3d center, TreeNode node, Point3d currentBest, double bestDist)        {            if (node == null)                return currentBest;            Point3d current = node.Value;            int d = node.Depth % this.dimension;            double coordCen = center[d];            double coordCur = current[d];            double dist = this.sqrDist(center, current);            if (dist >= 0.0 && dist < bestDist)            {                currentBest = current;                bestDist = dist;            }            dist = coordCen - coordCur;            if (bestDist < dist * dist)            {                currentBest = GetNeighbour(                    center, coordCen < coordCur ? node.LeftChild : node.RightChild, currentBest, bestDist);                bestDist = this.sqrDist(center, currentBest);            }            else            {                currentBest = GetNeighbour(center, node.LeftChild, currentBest, bestDist);                bestDist = this.sqrDist(center, currentBest);                currentBest = GetNeighbour(center, node.RightChild, currentBest, bestDist);                bestDist = this.sqrDist(center, currentBest);            }            return currentBest;        }         private void GetNeighboursAtDistance(Point3d center, double radius, TreeNode node, Point3dCollection points)        {            if (node == null) return;            Point3d current = node.Value;            double dist = this.sqrDist(center, current);            if (dist <= radius)            {                points.Add(current);            }            int d = node.Depth % this.dimension;            double coordCen = center[d];            double coordCur = current[d];            dist = coordCen - coordCur;            if (dist * dist > radius)            {                if (coordCen < coordCur)                {                    GetNeighboursAtDistance(center, radius, node.LeftChild, points);                }                else                {                    GetNeighboursAtDistance(center, radius, node.RightChild, points);                }            }            else            {                GetNeighboursAtDistance(center, radius, node.LeftChild, points);                GetNeighboursAtDistance(center, radius, node.RightChild, points);            }        }         private void GetKNeighbours(Point3d center, int number, TreeNode node, List<Tuple<double, Point3d>> pairs)        {            if (node == null) return;            Point3d current = node.Value;            double dist = this.sqrDist(center, current);            int cnt = pairs.Count;            if (cnt == 0)            {                pairs.Add(new Tuple<double, Point3d>(dist, current));            }            else if (cnt < number)            {                if (dist > pairs[0].Item1)                {                    pairs.Insert(0, new Tuple<double, Point3d>(dist, current));                }                else                {                    pairs.Add(new Tuple<double, Point3d>(dist, current));                }            }            else if (dist < pairs[0].Item1)            {                pairs[0] = new Tuple<double, Point3d>(dist, current);                pairs.Sort((p1, p2) => p2.Item1.CompareTo(p1.Item1));            }            int d = node.Depth % this.dimension;            double coordCen = center[d];            double coordCur = current[d];            dist = coordCen - coordCur;            if (dist * dist > pairs[0].Item1)            {                if (coordCen < coordCur)                {                    GetKNeighbours(center, number, node.LeftChild, pairs);                }                else                {                    GetKNeighbours(center, number, node.RightChild, pairs);                }            }            else            {                GetKNeighbours(center, number, node.LeftChild, pairs);                GetKNeighbours(center, number, node.RightChild, pairs);            }        }         private void FindRange(Point3d lowerLeft, Point3d upperRight, TreeNode node, Point3dCollection points)        {            if (node == null)                return;            Point3d current = node.Value;            if (ignoreZ)            {                if (current.X >= lowerLeft.X && current.X <= upperRight.X &&                    current.Y >= lowerLeft.Y && current.Y <= upperRight.Y)                    points.Add(current);            }            else            {                if (current.X >= lowerLeft.X && current.X <= upperRight.X &&                    current.Y >= lowerLeft.Y && current.Y <= upperRight.Y &&                    current.Z >= lowerLeft.Z && current.Z <= upperRight.Z)                    points.Add(current);            }            int d = node.Depth % this.dimension;            if (upperRight[d] < current[d])                FindRange(lowerLeft, upperRight, node.LeftChild, points);            else if (lowerLeft[d] > current[d])                FindRange(lowerLeft, upperRight, node.RightChild, points);            else            {                FindRange(lowerLeft, upperRight, node.LeftChild, points);                FindRange(lowerLeft, upperRight, node.RightChild, points);            }        }         private void GetConnexions(TreeNode node, double radius, List<Tuple<Point3d, Point3d>> connexions)        {            if (node == null) return;            Point3dCollection points = new Point3dCollection();            Point3d center = node.Value;            if (ignoreZ)            GetRightParentsNeighbours(center, node, radius, points);            GetNeighboursAtDistance(center, radius, node.LeftChild, points);            GetNeighboursAtDistance(center, radius, node.RightChild, points);            for (int i = 0; i < points.Count; i++)            {                connexions.Add(new Tuple<Point3d, Point3d>(center, points[i]));            }            GetConnexions(node.LeftChild, radius, connexions);            GetConnexions(node.RightChild, radius, connexions);        }         private void GetRightParentsNeighbours(Point3d center, TreeNode node, double radius, Point3dCollection points)        {            TreeNode parent = GetRightParent(node);            if (parent == null) return;            int d = parent.Depth % this.dimension;            double dist = center[d] - parent.Value[d];            if (dist * dist <= radius)            {                GetNeighboursAtDistance(center, radius, parent.RightChild, points);            }            GetRightParentsNeighbours(center, parent, radius, points);        }         private TreeNode GetRightParent(TreeNode node)        {            TreeNode parent = node.Parent;            if (parent == null) return null;            if (node.IsLeft) return parent;            return GetRightParent(parent);        }         private double SqrDistance2d(Point3d p1, Point3d p2)        {            return (p1.X - p2.X) * (p1.X - p2.X) +                (p1.Y - p2.Y) * (p1.Y - p2.Y);        }         private double SqrDistance3d(Point3d p1, Point3d p2)        {            return (p1.X - p2.X) * (p1.X - p2.X) +                 (p1.Y - p2.Y) * (p1.Y - p2.Y) +                 (p1.Z - p2.Z) * (p1.Z - p2.Z);        }         #endregion    }     static class Extensions    {        // Credit: Tony Tanzillo        // http://www.theswamp.org/index.php?topic=44312.msg495808#msg495808        public static T QuickSelectMedian<T>(this T[] items, Comparison<T> compare)        {            int l = items.Length;            int k = l / 2;            if (items == null || l == 0)                throw new ArgumentException("array");            int from = 0;            int to = l - 1;            while (from < to)            {                int r = from;                int w = to;                T current = items[(r + w) / 2];                while (r < w)                {                    if (compare(items[r], current) > -1)                    {                        var tmp = items[w];                        items[w] = items[r];                        items[r] = tmp;                        w--;                    }                    else                    {                        r++;                    }                }                if (compare(items[r], current) > 0)                {                    r--;                }                if (k <= r)                {                    to = r;                }                else                {                    from = r + 1;                }            }            return items[k];        }    }} `
« Last Edit: April 19, 2013, 01:05:53 PM by gile »
Speaking English as a French Frog

#### TheMaster

• Guest
##### Re: Kd Tree
« Reply #27 on: April 14, 2013, 02:59:15 PM »
Hi Tony,

It seems to me he didn't use a tree structure but a 'divide and conquer' algorithm as shown by this picture.
By my side, I tried to implement a more reusable (and may be usefull) structure.

I found another way to avoid duplicated connexions with the ConnectAll() method. Rather than storing nodes in a stack, I can get the 'right parents nodes' of the current node using a new property in the TreeNode which indicates if the the node is a left child node or not. This seems to improve a little ConnectAll() speed.

Hi Gile. Very good.

Here's one more suggestion: Depending on a number of factors like point distribution, there can be a very large number of leaf nodes in the tree (leaf nodes are nodes with no child nodes). You can save some memory by not having fields for child nodes on leaf nodes, by using two different classes for leaf and branch nodes, using something like this:

Code - C#: [Select]
` using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace Graph.Nodes{    /// Use for leaf nodes with no children:   /// (contains no fields for child nodes)    public class Node<T>   {      protected Node( T value )      {         this.Value = value;      }       public T Value      {         get;         set;      }       public virtual Node<T> Left      {         get         {            return null;         }         protected set         {         }      }       public virtual Node<T> Right      {         get         {            return null;         }         protected set         {         }      }       // Create leaf or branch node depending on      // if child nodes are provided:       public static Node<T> Create( T value, Node<T> left = null, Node<T> right = null )      {         if( left == null && right == null )            return new Node<T>( value );         else            return new BranchNode<T>( value, left, right );      }   }    /// Used for branch nodes with 1 or 2 children    public class BranchNode<T> : Node<T>   {      Node<T> left = null;      Node<T> right = null;       protected internal BranchNode( T value, Node<T> left, Node<T> right )          : base( value )      {         this.left = left;         this.right = right;      }       public override Node<T> Left      {         get         {            return left;         }         protected set         {            left = value;         }      }       public override Node<T> Right      {         get         {            return right;         }         protected set         {            right = value;         }      }   } }  `

#### gile

• Water Moccasin
• Posts: 2182
• Marseille, France
##### Re: Kd Tree
« Reply #28 on: April 14, 2013, 04:46:21 PM »
Thanks Tony, I'll give a try.
As the tree is balanced, there would never be more than half number of points plus one leaf nodes.
Speaking English as a French Frog

#### Jerry J

• Newt
• Posts: 48
##### Re: Kd Tree
« Reply #29 on: April 16, 2013, 04:07:34 AM »
Thanks for stretching my peanut of a brain.