gonum.org/v1/gonum@v0.14.0/spatial/barneshut/barneshut3_test.go (about) 1 // Copyright ©2019 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package barneshut 6 7 import ( 8 "fmt" 9 "math" 10 "reflect" 11 "testing" 12 13 "golang.org/x/exp/rand" 14 15 "gonum.org/v1/gonum/floats/scalar" 16 "gonum.org/v1/gonum/spatial/r3" 17 ) 18 19 type particle3 struct { 20 x, y, z, m float64 21 name string 22 } 23 24 func (p particle3) Coord3() r3.Vec { return r3.Vec{X: p.x, Y: p.y, Z: p.z} } 25 func (p particle3) Mass() float64 { return p.m } 26 27 var volumeTests = []struct { 28 name string 29 particles []particle3 30 want *Volume 31 }{ 32 { 33 name: "nil", 34 particles: nil, 35 want: &Volume{}, 36 }, 37 { 38 name: "empty", 39 particles: []particle3{}, 40 want: &Volume{Particles: []Particle3{}}, 41 }, 42 { 43 name: "one", 44 particles: []particle3{{m: 1}}, // Must have a mass to avoid vacuum decay. 45 want: &Volume{ 46 root: bucket{ 47 particle: particle3{x: 0, y: 0, z: 0, m: 1}, 48 bounds: r3.Box{Min: r3.Vec{X: 0, Y: 0, Z: 0}, Max: r3.Vec{X: 0, Y: 0, Z: 0}}, 49 center: r3.Vec{X: 0, Y: 0, Z: 0}, 50 mass: 1, 51 }, 52 53 Particles: []Particle3{ 54 particle3{x: 0, y: 0, z: 0, m: 1}, 55 }, 56 }, 57 }, 58 { 59 name: "3 corners", 60 particles: []particle3{ 61 {x: 1, y: 1, z: 1, m: 1}, 62 {x: -1, y: 1, z: 0, m: 1}, 63 {x: -1, y: -1, z: -1, m: 1}, 64 }, 65 want: &Volume{ 66 root: bucket{ 67 bounds: r3.Box{Min: r3.Vec{X: -1, Y: -1, Z: -1}, Max: r3.Vec{X: 1, Y: 1, Z: 1}}, 68 nodes: [8]*bucket{ 69 lnw: { 70 particle: particle3{x: -1, y: -1, z: -1, m: 1}, 71 bounds: r3.Box{Min: r3.Vec{X: -1, Y: -1, Z: -1}, Max: r3.Vec{X: 0, Y: 0, Z: 0}}, 72 center: r3.Vec{X: -1, Y: -1, Z: -1}, 73 mass: 1, 74 }, 75 use: { 76 particle: particle3{x: 1, y: 1, z: 1, m: 1}, 77 bounds: r3.Box{Min: r3.Vec{X: 0, Y: 0, Z: 0}, Max: r3.Vec{X: 1, Y: 1, Z: 1}}, 78 center: r3.Vec{X: 1, Y: 1, Z: 1}, 79 mass: 1, 80 }, 81 usw: { 82 particle: particle3{x: -1, y: 1, z: 0, m: 1}, 83 bounds: r3.Box{Min: r3.Vec{X: -1, Y: 0, Z: 0}, Max: r3.Vec{X: 0, Y: 1, Z: 1}}, 84 center: r3.Vec{X: -1, Y: 1, Z: 0}, 85 mass: 1, 86 }, 87 }, 88 center: r3.Vec{X: -0.3333333333333333, Y: 0.3333333333333333, Z: 0}, 89 mass: 3, 90 }, 91 92 Particles: []Particle3{ 93 particle3{x: 1, y: 1, z: 1, m: 1}, 94 particle3{x: -1, y: 1, z: 0, m: 1}, 95 particle3{x: -1, y: -1, z: -1, m: 1}, 96 }, 97 }, 98 }, 99 { 100 name: "4 corners", 101 particles: []particle3{ 102 {x: 1, y: 1, z: -1, m: 1}, 103 {x: -1, y: 1, z: 1, m: 1}, 104 {x: 1, y: -1, z: 1, m: 1}, 105 {x: -1, y: -1, z: -1, m: 1}, 106 }, 107 want: &Volume{ 108 root: bucket{ 109 bounds: r3.Box{Min: r3.Vec{X: -1, Y: -1, Z: -1}, Max: r3.Vec{X: 1, Y: 1, Z: 1}}, 110 nodes: [8]*bucket{ 111 lse: { 112 particle: particle3{x: 1, y: 1, z: -1, m: 1}, 113 bounds: r3.Box{Min: r3.Vec{X: 0, Y: 0, Z: -1}, Max: r3.Vec{X: 1, Y: 1, Z: 0}}, 114 center: r3.Vec{X: 1, Y: 1, Z: -1}, 115 mass: 1, 116 }, 117 lnw: { 118 particle: particle3{x: -1, y: -1, z: -1, m: 1}, 119 bounds: r3.Box{Min: r3.Vec{X: -1, Y: -1, Z: -1}, Max: r3.Vec{X: 0, Y: 0, Z: 0}}, 120 center: r3.Vec{X: -1, Y: -1, Z: -1}, 121 mass: 1, 122 }, 123 une: { 124 particle: particle3{x: 1, y: -1, z: 1, m: 1}, 125 bounds: r3.Box{Min: r3.Vec{X: 0, Y: -1, Z: 0}, Max: r3.Vec{X: 1, Y: 0, Z: 1}}, 126 center: r3.Vec{X: 1, Y: -1, Z: 1}, 127 mass: 1, 128 }, 129 usw: { 130 particle: particle3{x: -1, y: 1, z: 1, m: 1}, 131 bounds: r3.Box{Min: r3.Vec{X: -1, Y: 0, Z: 0}, Max: r3.Vec{X: 0, Y: 1, Z: 1}}, 132 center: r3.Vec{X: -1, Y: 1, Z: 1}, 133 mass: 1, 134 }, 135 }, 136 center: r3.Vec{X: 0, Y: 0, Z: 0}, 137 mass: 4, 138 }, 139 140 Particles: []Particle3{ 141 particle3{x: 1, y: 1, z: -1, m: 1}, 142 particle3{x: -1, y: 1, z: 1, m: 1}, 143 particle3{x: 1, y: -1, z: 1, m: 1}, 144 particle3{x: -1, y: -1, z: -1, m: 1}, 145 }, 146 }, 147 }, 148 { 149 name: "5 corners", 150 particles: []particle3{ 151 {x: 1, y: 1, z: -1, m: 1}, 152 {x: -1, y: 1, z: 1, m: 1}, 153 {x: 1, y: -1, z: 1, m: 1}, 154 {x: -1, y: -1, z: -1, m: 1}, 155 {x: -1.1, y: -1, z: -1.1, m: 1}, 156 }, 157 want: &Volume{ 158 root: bucket{ 159 bounds: r3.Box{Min: r3.Vec{X: -1.1, Y: -1, Z: -1.1}, Max: r3.Vec{X: 1, Y: 1, Z: 1}}, 160 nodes: [8]*bucket{ 161 lse: { 162 particle: particle3{x: 1, y: 1, z: -1, m: 1}, 163 bounds: r3.Box{Min: r3.Vec{X: -0.050000000000000044, Y: 0, Z: -1.1}, Max: r3.Vec{X: 1, Y: 1, Z: -0.050000000000000044}}, 164 center: r3.Vec{X: 1, Y: 1, Z: -1}, 165 mass: 1, 166 }, 167 lnw: { 168 bounds: r3.Box{Min: r3.Vec{X: -1.1, Y: -1, Z: -1.1}, Max: r3.Vec{X: -0.050000000000000044, Y: 0, Z: -0.050000000000000044}}, 169 nodes: [8]*bucket{ 170 lnw: { 171 bounds: r3.Box{Min: r3.Vec{X: -1.1, Y: -1, Z: -1.1}, Max: r3.Vec{X: -0.5750000000000001, Y: -0.5, Z: -0.5750000000000001}}, 172 nodes: [8]*bucket{ 173 lnw: { 174 bounds: r3.Box{Min: r3.Vec{X: -1.1, Y: -1, Z: -1.1}, Max: r3.Vec{X: -0.8375000000000001, Y: -0.75, Z: -0.8375000000000001}}, 175 nodes: [8]*bucket{ 176 lnw: { 177 bounds: r3.Box{Min: r3.Vec{X: -1.1, Y: -1, Z: -1.1}, Max: r3.Vec{X: -0.9687500000000001, Y: -0.875, Z: -0.9687500000000001}}, 178 nodes: [8]*bucket{ 179 lnw: { 180 particle: particle3{x: -1.1, y: -1, z: -1.1, m: 1}, 181 bounds: r3.Box{Min: r3.Vec{X: -1.1, Y: -1, Z: -1.1}, Max: r3.Vec{X: -1.034375, Y: -0.9375, Z: -1.034375}}, 182 center: r3.Vec{X: -1.1, Y: -1, Z: -1.1}, 183 mass: 1, 184 }, 185 une: { 186 particle: particle3{x: -1, y: -1, z: -1, m: 1}, 187 bounds: r3.Box{Min: r3.Vec{X: -1.034375, Y: -1, Z: -1.034375}, Max: r3.Vec{X: -0.9687500000000001, Y: -0.9375, Z: -0.9687500000000001}}, 188 center: r3.Vec{X: -1, Y: -1, Z: -1}, 189 mass: 1, 190 }, 191 }, 192 center: r3.Vec{X: -1.05, Y: -1, Z: -1.05}, 193 mass: 2, 194 }, 195 }, 196 center: r3.Vec{X: -1.05, Y: -1, Z: -1.05}, 197 mass: 2, 198 }, 199 }, 200 center: r3.Vec{X: -1.05, Y: -1, Z: -1.05}, 201 mass: 2, 202 }, 203 }, 204 center: r3.Vec{X: -1.05, Y: -1, Z: -1.05}, 205 mass: 2, 206 }, 207 une: { 208 particle: particle3{x: 1, y: -1, z: 1, m: 1}, 209 bounds: r3.Box{Min: r3.Vec{X: -0.050000000000000044, Y: -1, Z: -0.050000000000000044}, Max: r3.Vec{X: 1, Y: 0, Z: 1}}, 210 center: r3.Vec{X: 1, Y: -1, Z: 1}, 211 mass: 1, 212 }, 213 usw: { 214 particle: particle3{x: -1, y: 1, z: 1, m: 1}, 215 bounds: r3.Box{Min: r3.Vec{X: -1.1, Y: 0, Z: -0.050000000000000044}, Max: r3.Vec{X: -0.050000000000000044, Y: 1, Z: 1}}, 216 center: r3.Vec{X: -1, Y: 1, Z: 1}, 217 mass: 1, 218 }, 219 }, 220 center: r3.Vec{X: -0.22000000000000003, Y: -0.2, Z: -0.22000000000000003}, 221 mass: 5, 222 }, 223 Particles: []Particle3{ 224 particle3{x: 1, y: 1, z: -1, m: 1}, 225 particle3{x: -1, y: 1, z: 1, m: 1}, 226 particle3{x: 1, y: -1, z: 1, m: 1}, 227 particle3{x: -1, y: -1, z: -1, m: 1}, 228 particle3{x: -1.1, y: -1, z: -1.1, m: 1}, 229 }, 230 }, 231 }, 232 { 233 // This case is derived from the 2D example of the same name, 234 // but with a monotonic increase in Z position according to name. 235 name: "http://arborjs.org/docs/barnes-hut example", 236 particles: []particle3{ 237 {x: 64.5, y: 81.5, z: 0, m: 1, name: "A"}, 238 {x: 242, y: 34, z: 40, m: 1, name: "B"}, 239 {x: 199, y: 69, z: 80, m: 1, name: "C"}, 240 {x: 285, y: 106.5, z: 120, m: 1, name: "D"}, 241 {x: 170, y: 194.5, z: 160, m: 1, name: "E"}, 242 {x: 42.5, y: 334.5, z: 200, m: 1, name: "F"}, 243 {x: 147, y: 309, z: 240, m: 1, name: "G"}, 244 {x: 236.5, y: 324, z: 280, m: 1, name: "H"}, 245 }, 246 want: &Volume{ 247 root: bucket{ 248 bounds: r3.Box{Min: r3.Vec{X: 42.5, Y: 34, Z: 0}, Max: r3.Vec{X: 285, Y: 334.5, Z: 280}}, 249 nodes: [8]*bucket{ 250 lne: { 251 bounds: r3.Box{Min: r3.Vec{X: 163.75, Y: 34, Z: 0}, Max: r3.Vec{X: 285, Y: 184.25, Z: 140}}, 252 nodes: [8]*bucket{ 253 lne: { 254 particle: particle3{x: 242, y: 34, z: 40, m: 1, name: "B"}, 255 bounds: r3.Box{Min: r3.Vec{X: 224.375, Y: 34, Z: 0}, Max: r3.Vec{X: 285, Y: 109.125, Z: 70}}, 256 center: r3.Vec{X: 242, Y: 34, Z: 40}, 257 mass: 1, 258 }, 259 une: { 260 particle: particle3{x: 285, y: 106.5, z: 120, m: 1, name: "D"}, 261 bounds: r3.Box{Min: r3.Vec{X: 224.375, Y: 34, Z: 70}, Max: r3.Vec{X: 285, Y: 109.125, Z: 140}}, 262 center: r3.Vec{X: 285, Y: 106.5, Z: 120}, 263 mass: 1, 264 }, 265 unw: { 266 particle: particle3{x: 199, y: 69, z: 80, m: 1, name: "C"}, 267 bounds: r3.Box{Min: r3.Vec{X: 163.75, Y: 34, Z: 70}, Max: r3.Vec{X: 224.375, Y: 109.125, Z: 140}}, 268 center: r3.Vec{X: 199, Y: 69, Z: 80}, 269 mass: 1, 270 }, 271 }, 272 center: r3.Vec{X: 242, Y: 69.83333333333333, Z: 80}, 273 mass: 3, 274 }, 275 lnw: { 276 particle: particle3{x: 64.5, y: 81.5, z: 0, m: 1, name: "A"}, 277 bounds: r3.Box{Min: r3.Vec{X: 42.5, Y: 34, Z: 0}, Max: r3.Vec{X: 163.75, Y: 184.25, Z: 140}}, 278 center: r3.Vec{X: 64.5, Y: 81.5, Z: 0}, 279 mass: 1, 280 }, 281 (*bucket)(nil), 282 use: { 283 bounds: r3.Box{Min: r3.Vec{X: 163.75, Y: 184.25, Z: 140}, Max: r3.Vec{X: 285, Y: 334.5, Z: 280}}, 284 nodes: [8]*bucket{ 285 lnw: { 286 particle: particle3{x: 170, y: 194.5, z: 160, m: 1, name: "E"}, 287 bounds: r3.Box{Min: r3.Vec{X: 163.75, Y: 184.25, Z: 140}, Max: r3.Vec{X: 224.375, Y: 259.375, Z: 210}}, 288 center: r3.Vec{X: 170, Y: 194.5, Z: 160}, 289 mass: 1, 290 }, 291 use: { 292 particle: particle3{x: 236.5, y: 324, z: 280, m: 1, name: "H"}, 293 bounds: r3.Box{Min: r3.Vec{X: 224.375, Y: 259.375, Z: 210}, Max: r3.Vec{X: 285, Y: 334.5, Z: 280}}, 294 center: r3.Vec{X: 236.5, Y: 324, Z: 280}, 295 mass: 1, 296 }, 297 }, 298 center: r3.Vec{X: 203.25, Y: 259.25, Z: 220}, 299 mass: 2, 300 }, 301 usw: { 302 bounds: r3.Box{Min: r3.Vec{X: 42.5, Y: 184.25, Z: 140}, Max: r3.Vec{X: 163.75, Y: 334.5, Z: 280}}, 303 nodes: [8]*bucket{ 304 lsw: { 305 particle: particle3{x: 42.5, y: 334.5, z: 200, m: 1, name: "F"}, 306 bounds: r3.Box{Min: r3.Vec{X: 42.5, Y: 259.375, Z: 140}, Max: r3.Vec{X: 103.125, Y: 334.5, Z: 210}}, 307 center: r3.Vec{X: 42.5, Y: 334.5, Z: 200}, 308 mass: 1, 309 }, 310 use: { 311 particle: particle3{x: 147, y: 309, z: 240, m: 1, name: "G"}, 312 bounds: r3.Box{Min: r3.Vec{X: 103.125, Y: 259.375, Z: 210}, Max: r3.Vec{X: 163.75, Y: 334.5, Z: 280}}, 313 center: r3.Vec{X: 147, Y: 309, Z: 240}, 314 mass: 1, 315 }, 316 }, 317 center: r3.Vec{X: 94.75, Y: 321.75, Z: 220}, 318 mass: 2, 319 }, 320 }, 321 center: r3.Vec{X: 173.3125, Y: 181.625, Z: 140}, 322 mass: 8, 323 }, 324 325 Particles: []Particle3{ 326 particle3{x: 64.5, y: 81.5, z: 0, m: 1, name: "A"}, 327 particle3{x: 242, y: 34, z: 40, m: 1, name: "B"}, 328 particle3{x: 199, y: 69, z: 80, m: 1, name: "C"}, 329 particle3{x: 285, y: 106.5, z: 120, m: 1, name: "D"}, 330 particle3{x: 170, y: 194.5, z: 160, m: 1, name: "E"}, 331 particle3{x: 42.5, y: 334.5, z: 200, m: 1, name: "F"}, 332 particle3{x: 147, y: 309, z: 240, m: 1, name: "G"}, 333 particle3{x: 236.5, y: 324, z: 280, m: 1, name: "H"}, 334 }, 335 }, 336 }, 337 } 338 339 func TestVolume(t *testing.T) { 340 t.Parallel() 341 const tol = 1e-15 342 343 for _, test := range volumeTests { 344 var particles []Particle3 345 if test.particles != nil { 346 particles = make([]Particle3, len(test.particles)) 347 } 348 for i, p := range test.particles { 349 particles[i] = p 350 } 351 352 got, err := NewVolume(particles) 353 if err != nil { 354 t.Errorf("unexpected error: %v", err) 355 continue 356 } 357 358 if test.want != nil && !reflect.DeepEqual(got, test.want) { 359 t.Errorf("unexpected result for %q: got:%v want:%v", test.name, got, test.want) 360 } 361 362 // Recursively check all internal centers of mass. 363 walkVolume(&got.root, func(b *bucket) { 364 var sub []Particle3 365 walkVolume(b, func(b *bucket) { 366 if b.particle != nil { 367 sub = append(sub, b.particle) 368 } 369 }) 370 center, mass := centerOfMass3(sub) 371 if !scalar.EqualWithinAbsOrRel(center.X, b.center.X, tol, tol) || !scalar.EqualWithinAbsOrRel(center.Y, b.center.Y, tol, tol) || !scalar.EqualWithinAbsOrRel(center.Z, b.center.Z, tol, tol) { 372 t.Errorf("unexpected result for %q for center of mass: got:%f want:%f", test.name, b.center, center) 373 } 374 if !scalar.EqualWithinAbsOrRel(mass, b.mass, tol, tol) { 375 t.Errorf("unexpected result for %q for total mass: got:%f want:%f", test.name, b.mass, mass) 376 } 377 }) 378 } 379 } 380 381 func centerOfMass3(particles []Particle3) (center r3.Vec, mass float64) { 382 for _, p := range particles { 383 m := p.Mass() 384 mass += m 385 c := p.Coord3() 386 center.X += c.X * m 387 center.Y += c.Y * m 388 center.Z += c.Z * m 389 } 390 if mass != 0 { 391 center.X /= mass 392 center.Y /= mass 393 center.Z /= mass 394 } 395 return center, mass 396 } 397 398 func walkVolume(t *bucket, fn func(*bucket)) { 399 if t == nil { 400 return 401 } 402 fn(t) 403 for _, q := range t.nodes { 404 walkVolume(q, fn) 405 } 406 } 407 408 func TestVolumeForceOn(t *testing.T) { 409 t.Parallel() 410 const ( 411 size = 1000 412 tol = 1e-3 413 ) 414 for _, n := range []int{3e3, 1e4, 3e4} { 415 rnd := rand.New(rand.NewSource(1)) 416 particles := make([]Particle3, n) 417 for i := range particles { 418 particles[i] = particle3{x: size * rnd.Float64(), y: size * rnd.Float64(), z: size * rnd.Float64(), m: 1} 419 } 420 421 moved := make([]r3.Vec, n) 422 for i, p := range particles { 423 var v r3.Vec 424 m := p.Mass() 425 pv := p.Coord3() 426 for _, e := range particles { 427 v = r3.Add(v, Gravity3(p, e, m, e.Mass(), r3.Sub(e.Coord3(), pv))) 428 } 429 moved[i] = r3.Add(p.Coord3(), v) 430 } 431 432 volume, err := NewVolume(particles) 433 if err != nil { 434 t.Errorf("unexpected error: %v", err) 435 continue 436 } 437 for _, theta := range []float64{0, 0.3, 0.6, 0.9} { 438 theta := theta 439 t.Run(fmt.Sprintf("%d-body/theta=%v", len(particles), theta), func(t *testing.T) { 440 t.Parallel() 441 var ssd, sd float64 442 var calls int 443 for i, p := range particles { 444 v := volume.ForceOn(p, theta, func(p1, p2 Particle3, m1, m2 float64, v r3.Vec) r3.Vec { 445 calls++ 446 return Gravity3(p1, p2, m1, m2, v) 447 }) 448 pos := r3.Add(p.Coord3(), v) 449 d := r3.Sub(moved[i], pos) 450 ssd += d.X*d.X + d.Y*d.Y + d.Z*d.Z 451 sd += math.Hypot(math.Hypot(d.X, d.Y), d.Z) 452 } 453 rmsd := math.Sqrt(ssd / float64(len(particles))) 454 if rmsd > tol { 455 t.Error("RMSD for approximation too high") 456 } 457 t.Logf("rmsd=%.4v md=%.4v calls/particle=%.5v", 458 rmsd, sd/float64(len(particles)), float64(calls)/float64(len(particles))) 459 }) 460 } 461 } 462 } 463 464 var ( 465 fv3sink r3.Vec 466 volumeSink *Volume 467 ) 468 469 func BenchmarkNewVolume(b *testing.B) { 470 for _, n := range []int{1e3, 1e4, 1e5, 1e6} { 471 rnd := rand.New(rand.NewSource(1)) 472 particles := make([]Particle3, n) 473 for i := range particles { 474 particles[i] = particle3{x: rnd.Float64(), y: rnd.Float64(), z: rnd.Float64(), m: 1} 475 } 476 b.ResetTimer() 477 var err error 478 b.Run(fmt.Sprintf("%d-body", len(particles)), func(b *testing.B) { 479 for i := 0; i < b.N; i++ { 480 volumeSink, err = NewVolume(particles) 481 if err != nil { 482 b.Fatalf("unexpected error: %v", err) 483 } 484 } 485 }) 486 } 487 } 488 489 func BenchmarkVolumeForceOn(b *testing.B) { 490 for _, n := range []int{1e3, 1e4, 1e5} { 491 for _, theta := range []float64{0, 0.1, 0.5, 1, 1.5} { 492 if n > 1e4 && theta < 0.5 { 493 // Don't run unreasonably long benchmarks. 494 continue 495 } 496 rnd := rand.New(rand.NewSource(1)) 497 particles := make([]Particle3, n) 498 for i := range particles { 499 particles[i] = particle3{x: rnd.Float64(), y: rnd.Float64(), z: rnd.Float64(), m: 1} 500 } 501 volume, err := NewVolume(particles) 502 if err != nil { 503 b.Fatalf("unexpected error: %v", err) 504 } 505 b.ResetTimer() 506 b.Run(fmt.Sprintf("%d-body/theta=%v", len(particles), theta), func(b *testing.B) { 507 for i := 0; i < b.N; i++ { 508 for _, p := range particles { 509 fv3sink = volume.ForceOn(p, theta, Gravity3) 510 } 511 } 512 }) 513 } 514 } 515 } 516 517 func BenchmarkVolumeFull(b *testing.B) { 518 for _, n := range []int{1e3, 1e4, 1e5} { 519 for _, theta := range []float64{0, 0.1, 0.5, 1, 1.5} { 520 if n > 1e4 && theta < 0.5 { 521 // Don't run unreasonably long benchmarks. 522 continue 523 } 524 rnd := rand.New(rand.NewSource(1)) 525 particles := make([]Particle3, n) 526 for i := range particles { 527 particles[i] = particle3{x: rnd.Float64(), y: rnd.Float64(), z: rnd.Float64(), m: 1} 528 } 529 b.ResetTimer() 530 b.Run(fmt.Sprintf("%d-body/theta=%v", len(particles), theta), func(b *testing.B) { 531 for i := 0; i < b.N; i++ { 532 volume, err := NewVolume(particles) 533 if err != nil { 534 b.Fatalf("unexpected error: %v", err) 535 } 536 for _, p := range particles { 537 fv3sink = volume.ForceOn(p, theta, Gravity3) 538 } 539 } 540 }) 541 } 542 } 543 }