Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.Netron/3.0.2672.12446/Netron.Diagramming.Core-3.0.2672.12446/Layout/Force/NBodyForce.cs @ 2768

Last change on this file since 2768 was 2768, checked in by mkommend, 14 years ago

added solution folders and sources for the netron library (ticket #867)

File size: 17.5 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.Text;
5using Netron.Diagramming.Core.Analysis;
6namespace Netron.Diagramming.Core.Layout.Force
7{
8    /// <summary>
9    /// <para>
10    /// Force function which computes an n-body force such as gravity,
11    /// anti-gravity, or the results of electric charges. This function implements
12    /// the the Barnes-Hut algorithm for efficient n-body force simulations,
13    /// using a quad-tree with aggregated mass values to compute the n-body
14    /// force in O(N log N) time, where N is the number of ForceItems.</para>
15    ///
16    /// <para>The algorithm used is that of J. Barnes and P. Hut, in their research
17    /// paper <i>A Hierarchical  O(n log n) force calculation algorithm</i>, Nature,
18    ///  v.324, December 1986. For more details on the algorithm, see one of
19    ///  the following links --
20    /// <list type="bullet">
21    ///
22    ///   <item><a href="http://www.cs.berkeley.edu/~demmel/cs267/lecture26/lecture26.html">James Demmel's UC Berkeley lecture notes</a></item>
23    ///   <item><a href="http://www.physics.gmu.edu/~large/lr_forces/desc/bh/bhdesc.html">Description of the Barnes-Hut algorithm</a></item>
24    ///   <item><a href="http://www.ifa.hawaii.edu/~barnes/treecode/treeguide.html">Joshua Barnes' recent implementation</a></item>
25    /// </list></para>
26    /// </summary>
27    public class NBodyForce : AbstractForce
28    {
29
30        /*
31          The indexing scheme for quadtree child nodes goes row by row.
32            0 | 1    0 -> top left,    1 -> top right
33           -------
34            2 | 3    2 -> bottom left, 3 -> bottom right
35         */
36
37        #region Fields
38        private static String[] pnames = new String[] { "GravitationalConstant",
39            "Distance", "BarnesHutTheta"  };
40
41        public static float DEFAULT_GRAV_CONSTANT = -1.0f;
42        public static float DEFAULT_MIN_GRAV_CONSTANT = -10f;
43        public static float DEFAULT_MAX_GRAV_CONSTANT = 10f;
44
45        public static float DEFAULT_DISTANCE = -1f;
46        public static float DEFAULT_MIN_DISTANCE = -1f;
47        public static float DEFAULT_MAX_DISTANCE = 500f;
48
49        public static float DEFAULT_THETA = 0.9f;
50        public static float DEFAULT_MIN_THETA = 0.0f;
51        public static float DEFAULT_MAX_THETA = 1.0f;
52
53        public static int GRAVITATIONAL_CONST = 0;
54        public static int MIN_DISTANCE = 1;
55        public static int BARNES_HUT_THETA = 2;
56
57        private float xMin, xMax, yMin, yMax;
58        private QuadTreeNodeFactory factory = new QuadTreeNodeFactory();
59        private QuadTreeNode root;
60
61        private Random rand = new Random(12345678); // deterministic randomness
62
63        #endregion
64
65        #region Properties
66
67        /**
68         * Gets whether this is a force item.
69         */
70        public override bool IsItemForce
71        {
72            get
73            {
74                return true;
75            }
76        }
77
78        /// <summary>
79        /// Gets the parameter names.
80        /// </summary>
81        /// <value></value>
82        /// <returns></returns>
83        protected override String[] ParameterNames
84        {
85            get
86            {
87                return pnames;
88            }
89        }
90        #endregion
91
92        #region Constructor
93
94
95
96        /// <summary>
97        /// Create a new NBodyForce with default parameters.
98        /// </summary>
99        public NBodyForce()
100            : this(DEFAULT_GRAV_CONSTANT, DEFAULT_DISTANCE, DEFAULT_THETA)
101        {
102
103        }
104        #endregion
105
106        #region Methods
107        /// <summary>
108        /// Create a new NBodyForce.
109        /// </summary>
110        /// <param name="gravConstant">the gravitational constant to use. Nodes will attract each other if this value is positive, and will repel each other if it is negative.</param>
111        /// <param name="minDistance">the distance within which two particles will  interact. If -1, the value is treated as infinite.</param>
112        /// <param name="theta">the Barnes-Hut parameter theta, which controls when an aggregated mass is used rather than drilling down to individual  item mass values.</param>
113        public NBodyForce(float gravConstant, float minDistance, float theta)
114        {
115            parms = new float[] { gravConstant, minDistance, theta };
116            minValues = new float[] { DEFAULT_MIN_GRAV_CONSTANT,
117            DEFAULT_MIN_DISTANCE, DEFAULT_MIN_THETA };
118            maxValues = new float[] { DEFAULT_MAX_GRAV_CONSTANT,
119            DEFAULT_MAX_DISTANCE, DEFAULT_MAX_THETA };
120            root = factory.GetQuadTreeNode();
121        }
122
123
124
125
126        /// <summary>
127        /// Set the bounds of the region for which to compute the n-body simulation
128        /// </summary>
129        /// <param name="xMin">the minimum x-coordinate</param>
130        /// <param name="yMin">the minimum y-coordinate/param>
131        /// <param name="xMax"> the maximum x-coordinate</param>
132        /// <param name="yMax">the maximum y-coordinate</param>
133        private void setBounds(float xMin, float yMin, float xMax, float yMax)
134        {
135            this.xMin = xMin;
136            this.yMin = yMin;
137            this.xMax = xMax;
138            this.yMax = yMax;
139        }
140
141
142        /// <summary>
143        ///Clears the quadtree of all entries.
144        /// </summary>
145        public void clear()
146        {
147            ClearHelper(root);
148            root = factory.GetQuadTreeNode();
149        }
150
151        /// <summary>
152        /// Clearing aid
153        /// </summary>
154        /// <param name="n">The n.</param>
155        private void ClearHelper(QuadTreeNode n)
156        {
157            for (int i = 0; i < n.children.Length; i++)
158            {
159                if (n.children[i] != null)
160                    ClearHelper(n.children[i]);
161            }
162            factory.Reclaim(n);
163        }
164
165        /// <summary>
166        /// Initialize the simulation with the provided enclosing simulation. After
167        /// this call has been made, the simulation can be queried for the
168        /// n-body force acting on a given item.
169        /// </summary>
170        /// <param name="fsim">the encompassing ForceSimulator</param>       
171        public override void Init(ForceSimulator fsim)
172        {
173            clear(); // clear internal state
174
175            // compute and squarify bounds of quadtree
176            float x1 = float.MaxValue, y1 = float.MaxValue;
177            float x2 = float.MinValue, y2 = float.MinValue;
178            foreach (ForceItem item in fsim.Items)
179            {
180                float x = item.Location[0];
181                float y = item.Location[1];
182                if (x < x1) x1 = x;
183                if (y < y1) y1 = y;
184                if (x > x2) x2 = x;
185                if (y > y2) y2 = y;
186            }
187            float dx = x2 - x1, dy = y2 - y1;
188            if (dx > dy) { y2 = y1 + dx; } else { x2 = x1 + dy; }
189            setBounds(x1, y1, x2, y2);
190
191            // Insert items into quadtree
192            foreach (ForceItem item in fsim.Items)
193            {
194                Insert(item);
195            }
196
197            // calculate magnitudes and centers of mass
198            CalculateMass(root);
199        }
200
201        /// <summary>
202        /// Inserts an item into the quadtree.
203        /// </summary>
204        /// <param name="item"> the ForceItem to add.</param>
205        public void Insert(ForceItem item)
206        {
207            // Insert item into the quadtrees
208            try
209            {
210                Insert(item, root, xMin, yMin, xMax, yMax);
211            }
212            catch (StackOverflowException e)
213            {
214                // TODO: safe to remove?
215                Trace.WriteLine(e.Message);
216            }
217        }
218
219        /// <summary>
220        /// Inserts the specified force.
221        /// </summary>
222        /// <param name="p">The p.</param>
223        /// <param name="n">The n.</param>
224        /// <param name="x1">The x1.</param>
225        /// <param name="y1">The y1.</param>
226        /// <param name="x2">The x2.</param>
227        /// <param name="y2">The y2.</param>
228        private void Insert(ForceItem p, QuadTreeNode n, float x1, float y1, float x2, float y2)
229        {
230            // try to Insert particle p at node n in the quadtree
231            // by construction, each leaf will contain either 1 or 0 particles
232            if (n.hasChildren)
233            {
234                // n contains more than 1 particle
235                InsertHelper(p, n, x1, y1, x2, y2);
236            }
237            else if (n.value != null)
238            {
239                // n contains 1 particle
240                if (IsSameLocation(n.value, p))
241                {
242                    InsertHelper(p, n, x1, y1, x2, y2);
243                }
244                else
245                {
246                    ForceItem v = n.value; n.value = null;
247                    InsertHelper(v, n, x1, y1, x2, y2);
248                    InsertHelper(p, n, x1, y1, x2, y2);
249                }
250            }
251            else
252            {
253                // n is empty, so is a leaf
254                n.value = p;
255            }
256        }
257
258        /// <summary>
259        /// Determines whether the two force are at the same location.
260        /// </summary>
261        /// <param name="f1">The f1.</param>
262        /// <param name="f2">The f2.</param>
263        /// <returns>
264        ///   <c>true</c> if [is same location] [the specified f1]; otherwise, <c>false</c>.
265        /// </returns>
266        private static bool IsSameLocation(ForceItem f1, ForceItem f2)
267        {
268            float dx = Math.Abs(f1.Location[0] - f2.Location[0]);
269            float dy = Math.Abs(f1.Location[1] - f2.Location[1]);
270            return (dx < 0.01 && dy < 0.01);
271        }
272
273        /// <summary>
274        /// Inserts helper method.
275        /// </summary>
276        /// <param name="p">The p.</param>
277        /// <param name="n">The n.</param>
278        /// <param name="x1">The x1.</param>
279        /// <param name="y1">The y1.</param>
280        /// <param name="x2">The x2.</param>
281        /// <param name="y2">The y2.</param>
282        private void InsertHelper(ForceItem p, QuadTreeNode n, float x1, float y1, float x2, float y2)
283        {
284            float x = p.Location[0], y = p.Location[1];
285            float splitx = (x1 + x2) / 2;
286            float splity = (y1 + y2) / 2;
287            int i = (x >= splitx ? 1 : 0) + (y >= splity ? 2 : 0);
288            // create new child node, if necessary
289            if (n.children[i] == null)
290            {
291                n.children[i] = factory.GetQuadTreeNode();
292                n.hasChildren = true;
293            }
294            // update bounds
295            if (i == 1 || i == 3) x1 = splitx; else x2 = splitx;
296            if (i > 1) y1 = splity; else y2 = splity;
297            // recurse
298            Insert(p, n.children[i], x1, y1, x2, y2);
299        }
300
301        /// <summary>
302        /// Calculates the mass.
303        /// </summary>
304        /// <param name="n">The n.</param>
305        private void CalculateMass(QuadTreeNode n)
306        {
307            float xcom = 0, ycom = 0;
308            n.mass = 0;
309            if (n.hasChildren)
310            {
311                for (int i = 0; i < n.children.Length; i++)
312                {
313                    if (n.children[i] != null)
314                    {
315                        CalculateMass(n.children[i]);
316                        n.mass += n.children[i].mass;
317                        xcom += n.children[i].mass * n.children[i].com[0];
318                        ycom += n.children[i].mass * n.children[i].com[1];
319                    }
320                }
321            }
322            if (n.value != null)
323            {
324                n.mass += n.value.Mass;
325                xcom += n.value.Mass * n.value.Location[0];
326                ycom += n.value.Mass * n.value.Location[1];
327            }
328            n.com[0] = xcom / n.mass;
329            n.com[1] = ycom / n.mass;
330        }
331
332        /**
333         * Calculates the force vector acting on the given item.
334         * @param item the ForceItem for which to compute the force
335         */
336        public override void GetForce(ForceItem item)
337        {
338            try
339            {
340                ForceHelper(item, root, xMin, yMin, xMax, yMax);
341            }
342            catch (StackOverflowException e)
343            {
344                // TODO: safe to remove?
345                Trace.WriteLine(e.Message);
346            }
347        }
348
349        /// <summary>
350        /// Utility method.
351        /// </summary>
352        /// <param name="item">The item.</param>
353        /// <param name="n">The n.</param>
354        /// <param name="x1">The x1.</param>
355        /// <param name="y1">The y1.</param>
356        /// <param name="x2">The x2.</param>
357        /// <param name="y2">The y2.</param>
358        private void ForceHelper(ForceItem item, QuadTreeNode n, float x1, float y1, float x2, float y2)
359        {
360            float dx = n.com[0] - item.Location[0];
361            float dy = n.com[1] - item.Location[1];
362            float r = (float)Math.Sqrt(dx * dx + dy * dy);
363            bool same = false;
364            if (r == 0.0f)
365            {
366                // if items are in the exact same place, add some noise
367                dx = Convert.ToSingle((rand.NextDouble() - 0.5D) / 50.0D);
368                dy = Convert.ToSingle((rand.NextDouble() - 0.5D) / 50.0D);
369                r = (float)Math.Sqrt(dx * dx + dy * dy);
370                same = true;
371            }
372            bool minDist = parms[MIN_DISTANCE] > 0f && r > parms[MIN_DISTANCE];
373
374            // the Barnes-Hut approximation criteria is if the ratio of the
375            // size of the quadtree box to the distance between the point and
376            // the box's center of mass is beneath some threshold theta.
377            if ((!n.hasChildren && n.value != item) ||
378                 (!same && (x2 - x1) / r < parms[BARNES_HUT_THETA]))
379            {
380                if (minDist) return;
381                // either only 1 particle or we meet criteria
382                // for Barnes-Hut approximation, so calc force
383                float v = parms[GRAVITATIONAL_CONST] * item.Mass * n.mass
384                            / (r * r * r);
385                item.Force[0] += v * dx;
386                item.Force[1] += v * dy;
387            }
388            else if (n.hasChildren)
389            {
390                // recurse for more accurate calculation
391                float splitx = (x1 + x2) / 2;
392                float splity = (y1 + y2) / 2;
393                for (int i = 0; i < n.children.Length; i++)
394                {
395                    if (n.children != null && n.children[i] != null)
396                    {
397                        ForceHelper(item, n.children[i],
398                            (i == 1 || i == 3 ? splitx : x1), (i > 1 ? splity : y1),
399                            (i == 1 || i == 3 ? x2 : splitx), (i > 1 ? y2 : splity));
400                    }
401                }
402                if (minDist) return;
403                if (n.value != null && n.value != item)
404                {
405                    float v = parms[GRAVITATIONAL_CONST] * item.Mass * n.value.Mass
406                                / (r * r * r);
407                    item.Force[0] += v * dx;
408                    item.Force[1] += v * dy;
409                }
410            }
411        }
412        #endregion
413
414        #region Classes
415        /// <summary>
416        /// Represents a node in the quadtree.
417        /// </summary>
418        public sealed class QuadTreeNode
419        {
420            public QuadTreeNode()
421            {
422                com = new float[] { 0.0f, 0.0f };
423                children = new QuadTreeNode[4];
424            } //
425            public bool hasChildren = false;
426            public float mass; // total mass held by this node
427            public float[] com; // center of mass of this node
428            public ForceItem value; // ForceItem in this node, null if node has children
429            public QuadTreeNode[] children; // children nodes
430        }
431
432
433
434
435        ///<summary>
436        ///Helper class to minimize number of object creations across multiple uses of the quadtree.
437        ///</summary>
438        public sealed class QuadTreeNodeFactory
439        {
440            #region Fields
441            private int maxNodes = 50000;
442            private List<QuadTreeNode> nodes = new List<QuadTreeNode>();
443            #endregion
444
445            /// <summary>
446            /// Gets the quadtree node.
447            /// </summary>
448            /// <returns></returns>
449            public QuadTreeNode GetQuadTreeNode()
450            {
451                if (nodes.Count > 0)
452                {
453                    QuadTreeNode node = this.nodes[nodes.Count - 1];
454                    nodes.Remove(node);
455                    return node;
456                }
457                else
458                {
459                    return new QuadTreeNode();
460                }
461            }
462            /// <summary>
463            /// Reclaims the specified node.
464            /// </summary>
465            /// <param name="n">The n.</param>
466            public void Reclaim(QuadTreeNode n)
467            {
468                n.mass = 0;
469                n.com[0] = 0.0f; n.com[1] = 0.0f;
470                n.value = null;
471                n.hasChildren = false;
472                for (int i = 0; i < n.children.Length; i++)
473                {
474                    n.children[i] = null;
475                }
476                //n.children = null;
477                if (nodes.Count < maxNodes)
478                    nodes.Add(n);
479            }
480        }
481        #endregion
482
483    }
484
485}
Note: See TracBrowser for help on using the repository browser.