1 | using System;
|
---|
2 | using System.Collections.Generic;
|
---|
3 | using System.Diagnostics;
|
---|
4 | using System.Text;
|
---|
5 | using Netron.Diagramming.Core.Analysis;
|
---|
6 | namespace 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 | }
|
---|