From c9092a1e6ab610a7a0b4bcbe67077681dd599e15 Mon Sep 17 00:00:00 2001 From: Sebastien Binet Date: Thu, 28 Jan 2021 10:40:57 +0100 Subject: [PATCH] spatial/r2,all: harmonize r2.Vec API with r3.Vec API Migration to the new API can be achieved with this rsc.io/rf script: ``` rf ex { import "gonum.org/v1/gonum/spatial/r2"; var p,q r2.Vec; var f float64; p.Add(q) -> r2.Add(p, q); p.Sub(q) -> r2.Sub(p, q); p.Scale(f) -> r2.Scale(f, p); p.Dot(q) -> r2.Dot(p, q); p.Cross(q) -> r2.Cross(p, q); p.Rotate(f, q) -> r2.Rotate(p, f, q); } ``` Updates gonum/gonum#1553. --- graph/layout/eades.go | 12 +++---- spatial/barneshut/barneshut2.go | 8 ++--- spatial/barneshut/barneshut2_test.go | 8 ++--- spatial/r2/deprecated.go | 49 ++++++++++++++++++++++++++++ spatial/r2/vector.go | 43 ++++++++++++------------ spatial/r2/vector_test.go | 14 ++++---- 6 files changed, 93 insertions(+), 41 deletions(-) create mode 100644 spatial/r2/deprecated.go diff --git a/graph/layout/eades.go b/graph/layout/eades.go index a8e35ac6..7a83d165 100644 --- a/graph/layout/eades.go +++ b/graph/layout/eades.go @@ -84,7 +84,7 @@ func (u *EadesR2) Update(g graph.Graph, layout LayoutR2) bool { } var updated bool for i, p := range u.particles { - f := plane.ForceOn(p, u.Theta, barneshut.Gravity2).Scale(-u.Repulsion) + f := r2.Scale(-u.Repulsion, plane.ForceOn(p, u.Theta, barneshut.Gravity2)) // Prevent marginal updates that can be caused by // floating point error when nodes are very far apart. if math.Hypot(f.X, f.Y) > 1e-12 { @@ -137,16 +137,16 @@ func (u *EadesR2) Update(g graph.Graph, layout LayoutR2) bool { yidx := u.indexOf[yid] // Apply adjacent node attraction. - v := u.particles[yidx].Coord2().Sub(u.particles[xidx].Coord2()) - f := v.Scale(weight(xid, yid) * math.Log(math.Hypot(v.X, v.Y))) + v := r2.Sub(u.particles[yidx].Coord2(), u.particles[xidx].Coord2()) + f := r2.Scale(weight(xid, yid)*math.Log(math.Hypot(v.X, v.Y)), v) if math.IsInf(f.X, 0) || math.IsInf(f.Y, 0) { return false } if math.Hypot(f.X, f.Y) > 1e-12 { updated = true } - u.forces[xidx] = u.forces[xidx].Add(f) - u.forces[yidx] = u.forces[yidx].Sub(f) + u.forces[xidx] = r2.Add(u.forces[xidx], f) + u.forces[yidx] = r2.Sub(u.forces[yidx], f) } } @@ -160,7 +160,7 @@ func (u *EadesR2) Update(g graph.Graph, layout LayoutR2) bool { } for i, f := range u.forces { n := u.particles[i].(eadesR2Node) - n.pos = n.pos.Add(f.Scale(rate)) + n.pos = r2.Add(n.pos, r2.Scale(rate, f)) u.particles[i] = n layout.SetCoord2(n.id, n.pos) } diff --git a/spatial/barneshut/barneshut2.go b/spatial/barneshut/barneshut2.go index 08d52396..00a54b8e 100644 --- a/spatial/barneshut/barneshut2.go +++ b/spatial/barneshut/barneshut2.go @@ -37,7 +37,7 @@ func Gravity2(_, _ Particle2, m1, m2 float64, v r2.Vec) r2.Vec { if d2 == 0 { return r2.Vec{} } - return v.Scale((m1 * m2) / (d2 * math.Sqrt(d2))) + return r2.Scale((m1*m2)/(d2*math.Sqrt(d2)), v) } // Plane implements Barnes-Hut force approximation calculations. @@ -135,7 +135,7 @@ func (q *Plane) ForceOn(p Particle2, theta float64, f Force2) (force r2.Vec) { m := p.Mass() pv := p.Coord2() for _, e := range q.Particles { - v = v.Add(f(p, e, m, e.Mass(), e.Coord2().Sub(pv))) + v = r2.Add(v, f(p, e, m, e.Mass(), r2.Sub(e.Coord2(), pv))) } return v } @@ -260,7 +260,7 @@ func (t *tile) forceOn(p Particle2, pt r2.Vec, m, theta float64, f Force2) (vect s := ((t.bounds.Max.X - t.bounds.Min.X) + (t.bounds.Max.Y - t.bounds.Min.Y)) / 2 d := math.Hypot(pt.X-t.center.X, pt.Y-t.center.Y) if s/d < theta || t.particle != nil { - return f(p, t.particle, m, t.mass, t.center.Sub(pt)) + return f(p, t.particle, m, t.mass, r2.Sub(t.center, pt)) } var v r2.Vec @@ -268,7 +268,7 @@ func (t *tile) forceOn(p Particle2, pt r2.Vec, m, theta float64, f Force2) (vect if d == nil { continue } - v = v.Add(d.forceOn(p, pt, m, theta, f)) + v = r2.Add(v, d.forceOn(p, pt, m, theta, f)) } return v } diff --git a/spatial/barneshut/barneshut2_test.go b/spatial/barneshut/barneshut2_test.go index bca0d472..2bbb4303 100644 --- a/spatial/barneshut/barneshut2_test.go +++ b/spatial/barneshut/barneshut2_test.go @@ -427,9 +427,9 @@ func TestPlaneForceOn(t *testing.T) { m := p.Mass() pv := p.Coord2() for _, e := range particles { - v = v.Add(Gravity2(p, e, m, e.Mass(), e.Coord2().Sub(pv))) + v = r2.Add(v, Gravity2(p, e, m, e.Mass(), r2.Sub(e.Coord2(), pv))) } - moved[i] = p.Coord2().Add(v) + moved[i] = r2.Add(p.Coord2(), v) } plane, err := NewPlane(particles) @@ -448,8 +448,8 @@ func TestPlaneForceOn(t *testing.T) { calls++ return Gravity2(p1, p2, m1, m2, v) }) - pos := p.Coord2().Add(v) - d := moved[i].Sub(pos) + pos := r2.Add(p.Coord2(), v) + d := r2.Sub(moved[i], pos) ssd += d.X*d.X + d.Y*d.Y sd += math.Hypot(d.X, d.Y) } diff --git a/spatial/r2/deprecated.go b/spatial/r2/deprecated.go new file mode 100644 index 00000000..01141b62 --- /dev/null +++ b/spatial/r2/deprecated.go @@ -0,0 +1,49 @@ +// Copyright ©2021 The Gonum Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package r2 + +// TODO(sbinet): remove this file for Gonum-v0.10.0. + +// Add returns the vector sum of p and q. +// +// DEPRECATED: use r2.Add. +func (p Vec) Add(q Vec) Vec { + return Add(p, q) +} + +// Sub returns the vector sum of p and -q. +// +// DEPRECATED: use r2.Sub. +func (p Vec) Sub(q Vec) Vec { + return Sub(p, q) +} + +// Scale returns the vector p scaled by f. +// +// DEPRECATED: use r2.Scale. +func (p Vec) Scale(f float64) Vec { + return Scale(f, p) +} + +// Dot returns the dot product p·q. +// +// DEPRECATED: use r2.Dot. +func (p Vec) Dot(q Vec) float64 { + return Dot(p, q) +} + +// Cross returns the cross product p×q. +// +// DEPRECATED: use r2.Cross. +func (p Vec) Cross(q Vec) float64 { + return Cross(p, q) +} + +// Rotate returns a new vector, rotated by alpha around the provided point, q. +// +// DEPRECATED: use r2.Rotate. +func (p Vec) Rotate(alpha float64, q Vec) Vec { + return Rotate(p, alpha, q) +} diff --git a/spatial/r2/vector.go b/spatial/r2/vector.go index 0a19c196..18c1f390 100644 --- a/spatial/r2/vector.go +++ b/spatial/r2/vector.go @@ -12,38 +12,41 @@ type Vec struct { } // Add returns the vector sum of p and q. -func (p Vec) Add(q Vec) Vec { - p.X += q.X - p.Y += q.Y - return p +func Add(p, q Vec) Vec { + return Vec{ + X: p.X + q.X, + Y: p.Y + q.Y, + } } // Sub returns the vector sum of p and -q. -func (p Vec) Sub(q Vec) Vec { - p.X -= q.X - p.Y -= q.Y - return p +func Sub(p, q Vec) Vec { + return Vec{ + X: p.X - q.X, + Y: p.Y - q.Y, + } } // Scale returns the vector p scaled by f. -func (p Vec) Scale(f float64) Vec { - p.X *= f - p.Y *= f - return p +func Scale(f float64, p Vec) Vec { + return Vec{ + X: f * p.X, + Y: f * p.Y, + } } // Dot returns the dot product p·q. -func (p Vec) Dot(q Vec) float64 { +func Dot(p, q Vec) float64 { return p.X*q.X + p.Y*q.Y } // Cross returns the cross product p×q. -func (p Vec) Cross(q Vec) float64 { +func Cross(p, q Vec) float64 { return p.X*q.Y - p.Y*q.X } // Rotate returns a new vector, rotated by alpha around the provided point, q. -func (p Vec) Rotate(alpha float64, q Vec) Vec { +func Rotate(p Vec, alpha float64, q Vec) Vec { return NewRotation(alpha, q).Rotate(p) } @@ -65,12 +68,12 @@ func Unit(p Vec) Vec { if p.X == 0 && p.Y == 0 { return Vec{X: math.NaN(), Y: math.NaN()} } - return p.Scale(1 / Norm(p)) + return Scale(1/Norm(p), p) } // Cos returns the cosine of the opening angle between p and q. func Cos(p, q Vec) float64 { - return p.Dot(q) / (Norm(p) * Norm(q)) + return Dot(p, q) / (Norm(p) * Norm(q)) } // Box is a 2D bounding box. @@ -98,11 +101,11 @@ func (r Rotation) Rotate(p Vec) Vec { if r.isIdentity() { return p } - o := p.Sub(r.p) - return Vec{ + o := Sub(p, r.p) + return Add(Vec{ X: (o.X*r.cos - o.Y*r.sin), Y: (o.X*r.sin + o.Y*r.cos), - }.Add(r.p) + }, r.p) } func (r Rotation) isIdentity() bool { diff --git a/spatial/r2/vector_test.go b/spatial/r2/vector_test.go index ed16dc37..18f9014b 100644 --- a/spatial/r2/vector_test.go +++ b/spatial/r2/vector_test.go @@ -22,7 +22,7 @@ func TestAdd(t *testing.T) { {Vec{1, -3}, Vec{1, -6}, Vec{2, -9}}, {Vec{1, 2}, Vec{-1, -2}, Vec{}}, } { - got := test.v1.Add(test.v2) + got := Add(test.v1, test.v2) if got != test.want { t.Errorf( "error: %v + %v: got=%v, want=%v", @@ -43,7 +43,7 @@ func TestSub(t *testing.T) { {Vec{1, -3}, Vec{1, -6}, Vec{0, 3}}, {Vec{1, 2}, Vec{1, 2}, Vec{}}, } { - got := test.v1.Sub(test.v2) + got := Sub(test.v1, test.v2) if got != test.want { t.Errorf( "error: %v - %v: got=%v, want=%v", @@ -67,7 +67,7 @@ func TestScale(t *testing.T) { {2, Vec{1, -3}, Vec{2, -6}}, {10, Vec{1, 2}, Vec{10, 20}}, } { - got := test.v.Scale(test.a) + got := Scale(test.a, test.v) if got != test.want { t.Errorf( "error: %v * %v: got=%v, want=%v", @@ -89,7 +89,7 @@ func TestDot(t *testing.T) { {Vec{1, 2}, Vec{-0.3, 0.4}, 0.5}, } { { - got := test.u.Dot(test.v) + got := Dot(test.u, test.v) if got != test.want { t.Errorf( "error: %v · %v: got=%v, want=%v", @@ -98,7 +98,7 @@ func TestDot(t *testing.T) { } } { - got := test.v.Dot(test.u) + got := Dot(test.v, test.u) if got != test.want { t.Errorf( "error: %v · %v: got=%v, want=%v", @@ -120,7 +120,7 @@ func TestCross(t *testing.T) { {Vec{1, 2}, Vec{-4, 5}, 13}, {Vec{1, 2}, Vec{2, 3}, -1}, } { - got := test.v1.Cross(test.v2) + got := Cross(test.v1, test.v2) if got != test.want { t.Errorf( "error: %v × %v = %v, want %v", @@ -236,7 +236,7 @@ func TestRotate(t *testing.T) { {Vec{2, 2}, Vec{1, 1}, math.Pi, Vec{0, 0}}, {Vec{2, 2}, Vec{2, 0}, math.Pi, Vec{2, -2}}, } { - got := test.v.Rotate(test.alpha, test.q) + got := Rotate(test.v, test.alpha, test.q) if !vecApproxEqual(got, test.want) { t.Errorf( "rotate(%v, %v, %v)= %v, want=%v",