123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 | # n-body, from The Computer Language Benchmarks Game
#
# A Tomo port. The simulation uses NO inline C; the only C_code use is the
# final printf() calls, which format the energy to 9 decimal places (the
# benchmark's required output format). All the actual computation is pure Tomo.
#
# Design notes (for performance without giving up readability):
# - Vec3 has inline arithmetic metamethods, so the physics reads as vector
# math (`a - b`, `v*k`, `v.len2()`) rather than component bookkeeping.
# - Each body is one `Body` struct in a single `&[Body]` array (stack header,
# heap payload; it never escapes `main`), so one
# bounds-checked access yields a body's position, velocity, and mass
# (all adjacent in memory) instead of indexing three parallel arrays.
# - Full sweeps use element iteration (`for b in bodies` to read,
# `for &b in bodies` to update in place), which skips per-index bounds
# checks and per-write copy-on-write guards; loop counters/indices are
# native `Int64`, not the default arbitrary-precision `Int`.
# - Pairwise work reads as `for bi, bj in bodies.pairs()` (each distinct
# pair once); in for-position that compiles to an inline double-index
# loop, no iterator closure.
#
# Usage: nbody <steps> (e.g. ./nbody 50000000)
struct Vec3{x, y, z: Float64}
func plus(a, b: Vec3 -> Vec3; inline)
return Vec3{a.x + b.x, a.y + b.y, a.z + b.z}
func minus(a, b: Vec3 -> Vec3; inline)
return Vec3{a.x - b.x, a.y - b.y, a.z - b.z}
func scaled_by(v:Vec3, k:Float64 -> Vec3; inline)
return Vec3{v.x*k, v.y*k, v.z*k}
func len2(a: Vec3 -> Float64; inline)
return a.x*a.x + a.y*a.y + a.z*a.z
struct Body{pos, vel: Vec3, mass: Float64}
func print9(x:Float64)
# Format-only inline C: print `x` with %.9f, matching the reference output.
C_code `printf("%.9f", @x); putchar(10);`
func advance(bodies:&[Body], steps:Int64, dt:Float64)
n := Int64(bodies.length)
for _ in Int64(1).to(steps)
# The outer body is held by reference (`&bi`): direct field reads and a
# single field write, no per-element checks. The inner `bodies[j]`
# cross-accesses stay indexed (checked), since they're random-access by
# nature. The list stays live inside a `for &` loop, so indexed reads
# and writes through `bodies` are fine; only resizing/copying it
# mid-loop is (loudly) disallowed.
for &bi at i in bodies
if i == n
break
# Hoist body i's fields into locals; accumulate velocity locally.
pos_i := bi.pos
mass_i := bi.mass
vi := bi.vel
for j in (i+1).to(n)
bj := bodies[j]!
d := pos_i - bj.pos
d2 := d.len2()
mag := dt / (d2 * Float64.sqrt(d2)!)
vi -= d * (bj.mass * mag)
bodies[j] = Body{bj.pos, bj.vel + d * (mass_i * mag), bj.mass}
bi.vel = vi
for &b in bodies
b.pos += b.vel * dt
func energy(bodies:[Body] -> Float64)
e := (+: 0.5 * b.mass * b.vel.len2() for b in bodies) or 0.0
# Potential energy over each distinct pair of bodies:
for bi, bj in bodies.pairs()
d := bi.pos - bj.pos
e -= (bi.mass * bj.mass) / Float64.sqrt(d.len2())!
return e
func main(steps:Int64)
dpy : Float64 = 365.24
sm := 4.0 * Float64.PI * Float64.PI
# Bodies: sun, jupiter, saturn, uranus, neptune.
bodies := &[
Body{Vec3{0.0, 0.0, 0.0}, Vec3{0.0, 0.0, 0.0}, sm},
Body{
Vec3{4.84143144246472090e00, -1.16032004402742839e00, -1.03622044471123109e-01},
Vec3{1.66007664274403694e-03*dpy, 7.69901118419740425e-03*dpy, -6.90460016972063023e-05*dpy},
9.54791938424326609e-04 * sm},
Body{
Vec3{8.34336671824457987e00, 4.12479856412430479e00, -4.03523417114321381e-01},
Vec3{-2.76742510726862411e-03*dpy, 4.99852801234917238e-03*dpy, 2.30417297573763929e-05*dpy},
2.85885980666130812e-04 * sm},
Body{
Vec3{1.28943695621391310e01, -1.51111514016986312e01, -2.23307578892655734e-01},
Vec3{2.96460137564761618e-03*dpy, 2.37847173959480950e-03*dpy, -2.96589568540237556e-05*dpy},
4.36624404335156298e-05 * sm},
Body{
Vec3{1.53796971148509165e01, -2.59193146099879641e01, 1.79258772950371181e-01},
Vec3{2.68067772490389322e-03*dpy, 1.62824170038242295e-03*dpy, -9.51592254519715870e-05*dpy},
5.15138902046611451e-05 * sm},
]
# offset_momentum: pin the total momentum to zero via the sun's velocity.
p := Vec3{0.0, 0.0, 0.0}
for b in bodies
p += b.vel * b.mass
sun := bodies[1]!
bodies[1] = Body{sun.pos, p * (-1.0 / sm), sun.mass}
print9(energy(bodies[]))
advance(bodies, steps, 0.01)
print9(energy(bodies[]))
|