2

I'm trying to find the mid-point between a set of turtles in a world that wraps, both along the x- and y-axis. The obvious solution is to

let mid-point list mean [xcor] turtles mean [ycor] turtles

but when the world wraps, this returns a result that isn't exactly what I'm looking for. I.e. if two turtles are at respectively [0 -25] and [0 23] in a 25 x 25 world, I would like to return [0 24] (or maybe [0 24.5]?). But the mean-approach returns [0 1]. Does anyone have a good suggestion for how to do this? One approach would be to find the patch that has the lowest sum of distances to the relevant turtles, but I'd prefer to find the exact point.

Does this make sense? Any suggestions?

Arthur Hjorth
  • 889
  • 4
  • 10
  • Can you use the mod command `let mid-point list mean [xcor mod 25] turtles mean [ycor mod 25] turtles` – Salix alba Mar 23 '14 at 20:01
  • Hmm. This could be tough because I doubt a unique “right” answer exists. Consider a bunch of points scattered evenly around the torus — the “center” is everywhere and nowhere. – Seth Tisue Mar 23 '14 at 20:39

5 Answers5

2

I first misread the question and thought it was a matter of finding the midpoint between only two turtles, so I suggested this:

to setup
  clear-all
  resize-world -25 25 -25 25
  create-turtles 2
  ask turtle 0 [ setxy 0 -25 ]
  ask turtle 1 [ setxy 0 23 ]
  show midpoint turtle 0 turtle 1
end

to-report midpoint [ t1 t2 ]
  let result (list)
  ask t1 [
    hatch 1 [
      face t2
      forward distance t2 / 2
      set result list xcor ycor
      die
    ]
  ]
  report result
end

Provided world wrapping is turned on, running setup will print [0 24.5].

But Arthur pointed out that what he actually wanted was to find the midpoint for a whole set of turtles (i.e., a centroid). After a some thinking, I realized I could apply a very similar method:

  • Create one temporary turtle for each one in the original set (I call them points);
  • Make the first one of those a "seeker" turtle that will move around until it finds the centroid.
  • Have the seeker move half-way to the first of the other points, then a third of the way to the second one, then a fourth of the way to the third one, etc., until there are no more points left.

Aside from intuition, I have no mathematical proof that this works, but when world wrapping is turned off, it does give the same result as the "mean coordinates" method, so I assume it also works with wrapping on. It also works for the [0 24.5] case. I'd be happy to get corrected if I'm mistaken, though.

Here it is:

to setup
  clear-all
  ask n-of (2 + random 10) patches [ sprout 1 ]

  ; Show centroid with the "seeker" method
  show centroid turtles

  ; Show centroid calculated with mean coordinates for comparison.
  ; If wrapping is on, it will probably be different from
  ; `centroid turtles`. If wrapping is off, it should be the
  ; same, with very small floating point variations:  
  show list mean [xcor] of turtles mean [ycor] of turtles  

end

to-report centroid [ set-of-turtles ]
  let points (list)
  ask set-of-turtles [
    hatch 1 [ set points lput self points ]
  ]
  report seek-centroid first points but-first points 2
end

to-report seek-centroid [ seeker points n ]
  if-else not empty? points [
    let target first points
    ask seeker [
      face target
      forward distance target / n
    ]
    ask target [ die ]
    report seek-centroid seeker but-first points (n + 1)
  ]
  [
    let result [ list xcor ycor ] of seeker
    ask seeker [ die ]
    report result
  ]
end

Note: adding ask first points [ pen-down ] before calling report seek-centroid ... is a fun way to get a feel for how the algorithm works.

Nicolas Payette
  • 14,847
  • 1
  • 27
  • 37
  • I'm sorry - maybe my example was bad and made it sound like I want this for just two turtles. I really am interested in this for a turtleset consisting of any (reasonable) number of turtles. – Arthur Hjorth Mar 23 '14 at 20:29
  • Your question if fine. I'm the one who misread. But considering the simpler "two turtles" case first is what eventually led me to a general solution (given in my edited answer), so I guess all is well. (Assuming my general solution is right, of course...) – Nicolas Payette Mar 23 '14 at 22:04
  • This works, thank you so much. I just implemented this in a k-means clustering model that might go in the NetLogo models library. If it does, we'll credit you for this. Thanks again! – Arthur Hjorth Mar 23 '14 at 23:47
  • Great! I'd be happy if it went in the library! Speaking of k-means, were you aware of https://github.com/NetLogo/K-Means-Extension ? (It does not, however, support world wrapping like this. Still, it might be worth mentioning in your model's info tab.) – Nicolas Payette Mar 24 '14 at 00:18
2

Think of the 1D case with points in a unit interval. You can visualise this as points lying round a circle, and you want some sort of notion of average of the points. This comes under field of [Directional statistics] (https://en.wikipedia.org/wiki/Directional_statistics) and is a non-trivial thing to work with. One way might be to think of the circle as lying in the plane, take the average of these, calculate its angle giving a point on the circle and then a point on the circle.

Wikipedia mentions "A simple way to calculate the mean of a series of angles (in the interval [0°, 360°)) is to calculate the mean of the cosines and sines of each angle, and obtain the angle by calculating the inverse tangent." There is an article Mean of circular quantities which I think gives you the concept of mean your thinking of.

You could treat x and y separately. Calculating

let X-sin-mean mean [sin (xcor * 2 * pi /25)]
let X-cos-mean mean [cos (xcor * 2 * pi /25)]
let Y-sin-mean mean [sin (ycor * 2 * pi /25)]
let Y-cos-mean mean [cos (ycor * 2 * pi /25)]
let X-angle-mean [atan2 X-sin-mean X-cos-mean]
let Y-angle-mean [atan2 Y-sin-mean Y-cos-mean]
let X-mean (X-angle-mean * 25 / ( 2 * pi))
let Y-mean (Y-angle-mean * 25 / ( 2 * pi))

my netlogo syntax might be a bit faulty. I hope you get the gist.

Salix alba
  • 7,536
  • 2
  • 32
  • 38
  • I am not sure my math is strong enough to make sense of this without actually implementing it in code. However, Nicolas' example uses what I see as good 'turtle behavior' to find the right place, so I will go with that. Thank you so much, though! – Arthur Hjorth Mar 23 '14 at 23:49
2

To get the patch that contains the center point, you can do

min-one-of patches [ sum [ distance myself ] of turtles ]
Bryan Head
  • 12,360
  • 5
  • 32
  • 50
  • Thanks for this one, Bryan. It's definitely the least verbose version, although it 'only' gets the patch rather than the actual point. I guess with a high enough patch number, it would get close to the actual point here. – Arthur Hjorth Mar 24 '14 at 12:37
  • It is worth noting that Bryan's method and mine do not always give the same answer (whether or not wrapping is turned on). I'm not sure why that is. His method being simpler and making more intuitive sense, though, I'd be tempted to go with it. Especially since just getting the centroid _patch_ is good enough for k-means. It might be slower, though, since it's O(|P|*|T|) instead of O(|T|). – Nicolas Payette Mar 24 '14 at 15:33
2

A question on the mean heading of turtles got me thinking of another solution to this problem based on circular means. Basically, you can just take the circular mean of the x-coordinates and y-coordinates. Here's the code:

;; lower and upper should be equivalent on the circle, but upper > lower in the reals
;; For circles, lower = 0 and upper = 360.
;; For clocks, lower = 0 and upper = 12 (or 24)
;; For x-coordinates, lower = min-pxcor - .5 and upper = max-pxcor + .5
to-report circular-mean [ lower upper vals ]
  let width upper - lower
  let angles map [ 360 * (? - lower) / width ] vals
  let mean-x mean map [ cos ? ] angles
  let mean-y mean map [ sin ? ] angles

  ; not doing turtle headings here, so we flip atan's arguments
  report width * (atan mean-y mean-x) / 360 + lower
end

to-report mean-xcor [ xs ]
  report (circular-mean (min-pxcor - .5) (max-pxcor + .5) xs)
end

to-report mean-ycor [ ys ]
  report (circular-mean (min-pycor - .5) (max-pycor + .5) ys)
end

This really treats the two dimensions as circular. If you think of a dimension as a circle, this finds the point on that circle that has the smallest square distance to all the other points, where distance here is the euclidean distance between the points, rather than the distance around the circle (which is what most of the answers have been using).

Community
  • 1
  • 1
Bryan Head
  • 12,360
  • 5
  • 32
  • 50
  • Woops, just noticed that Salix already posted an answer based on the same method. I'll leave this up as it is fully functioning code though. – Bryan Head Jul 18 '14 at 16:53
0

This SO question has several answers that discuss both how hard this problem is and a few algorithms for solving it. Unfortunately, it is not trivial. The algorithms are all iterative (e.g. variations of gradient descent) and the solution space has many local optima.

"Center of Mass" between a set of points on a Toroidally-Wrapped Map that minimizes average distance to all points

Community
  • 1
  • 1
Bryan Head
  • 12,360
  • 5
  • 32
  • 50