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.
This commit is contained in:
Sebastien Binet
2021-01-28 10:40:57 +01:00
parent 3199e478a1
commit c9092a1e6a
6 changed files with 93 additions and 41 deletions

View File

@@ -84,7 +84,7 @@ func (u *EadesR2) Update(g graph.Graph, layout LayoutR2) bool {
} }
var updated bool var updated bool
for i, p := range u.particles { 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 // Prevent marginal updates that can be caused by
// floating point error when nodes are very far apart. // floating point error when nodes are very far apart.
if math.Hypot(f.X, f.Y) > 1e-12 { 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] yidx := u.indexOf[yid]
// Apply adjacent node attraction. // Apply adjacent node attraction.
v := u.particles[yidx].Coord2().Sub(u.particles[xidx].Coord2()) v := r2.Sub(u.particles[yidx].Coord2(), u.particles[xidx].Coord2())
f := v.Scale(weight(xid, yid) * math.Log(math.Hypot(v.X, v.Y))) 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) { if math.IsInf(f.X, 0) || math.IsInf(f.Y, 0) {
return false return false
} }
if math.Hypot(f.X, f.Y) > 1e-12 { if math.Hypot(f.X, f.Y) > 1e-12 {
updated = true updated = true
} }
u.forces[xidx] = u.forces[xidx].Add(f) u.forces[xidx] = r2.Add(u.forces[xidx], f)
u.forces[yidx] = u.forces[yidx].Sub(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 { for i, f := range u.forces {
n := u.particles[i].(eadesR2Node) 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 u.particles[i] = n
layout.SetCoord2(n.id, n.pos) layout.SetCoord2(n.id, n.pos)
} }

View File

@@ -37,7 +37,7 @@ func Gravity2(_, _ Particle2, m1, m2 float64, v r2.Vec) r2.Vec {
if d2 == 0 { if d2 == 0 {
return r2.Vec{} 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. // 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() m := p.Mass()
pv := p.Coord2() pv := p.Coord2()
for _, e := range q.Particles { 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 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 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) d := math.Hypot(pt.X-t.center.X, pt.Y-t.center.Y)
if s/d < theta || t.particle != nil { 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 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 { if d == nil {
continue continue
} }
v = v.Add(d.forceOn(p, pt, m, theta, f)) v = r2.Add(v, d.forceOn(p, pt, m, theta, f))
} }
return v return v
} }

View File

@@ -427,9 +427,9 @@ func TestPlaneForceOn(t *testing.T) {
m := p.Mass() m := p.Mass()
pv := p.Coord2() pv := p.Coord2()
for _, e := range particles { 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) plane, err := NewPlane(particles)
@@ -448,8 +448,8 @@ func TestPlaneForceOn(t *testing.T) {
calls++ calls++
return Gravity2(p1, p2, m1, m2, v) return Gravity2(p1, p2, m1, m2, v)
}) })
pos := p.Coord2().Add(v) pos := r2.Add(p.Coord2(), v)
d := moved[i].Sub(pos) d := r2.Sub(moved[i], pos)
ssd += d.X*d.X + d.Y*d.Y ssd += d.X*d.X + d.Y*d.Y
sd += math.Hypot(d.X, d.Y) sd += math.Hypot(d.X, d.Y)
} }

49
spatial/r2/deprecated.go Normal file
View File

@@ -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)
}

View File

@@ -12,38 +12,41 @@ type Vec struct {
} }
// Add returns the vector sum of p and q. // Add returns the vector sum of p and q.
func (p Vec) Add(q Vec) Vec { func Add(p, q Vec) Vec {
p.X += q.X return Vec{
p.Y += q.Y X: p.X + q.X,
return p Y: p.Y + q.Y,
}
} }
// Sub returns the vector sum of p and -q. // Sub returns the vector sum of p and -q.
func (p Vec) Sub(q Vec) Vec { func Sub(p, q Vec) Vec {
p.X -= q.X return Vec{
p.Y -= q.Y X: p.X - q.X,
return p Y: p.Y - q.Y,
}
} }
// Scale returns the vector p scaled by f. // Scale returns the vector p scaled by f.
func (p Vec) Scale(f float64) Vec { func Scale(f float64, p Vec) Vec {
p.X *= f return Vec{
p.Y *= f X: f * p.X,
return p Y: f * p.Y,
}
} }
// Dot returns the dot product p·q. // 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 return p.X*q.X + p.Y*q.Y
} }
// Cross returns the cross product p×q. // 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 return p.X*q.Y - p.Y*q.X
} }
// Rotate returns a new vector, rotated by alpha around the provided point, q. // 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) return NewRotation(alpha, q).Rotate(p)
} }
@@ -65,12 +68,12 @@ func Unit(p Vec) Vec {
if p.X == 0 && p.Y == 0 { if p.X == 0 && p.Y == 0 {
return Vec{X: math.NaN(), Y: math.NaN()} 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. // Cos returns the cosine of the opening angle between p and q.
func Cos(p, q Vec) float64 { 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. // Box is a 2D bounding box.
@@ -98,11 +101,11 @@ func (r Rotation) Rotate(p Vec) Vec {
if r.isIdentity() { if r.isIdentity() {
return p return p
} }
o := p.Sub(r.p) o := Sub(p, r.p)
return Vec{ return Add(Vec{
X: (o.X*r.cos - o.Y*r.sin), X: (o.X*r.cos - o.Y*r.sin),
Y: (o.X*r.sin + o.Y*r.cos), Y: (o.X*r.sin + o.Y*r.cos),
}.Add(r.p) }, r.p)
} }
func (r Rotation) isIdentity() bool { func (r Rotation) isIdentity() bool {

View File

@@ -22,7 +22,7 @@ func TestAdd(t *testing.T) {
{Vec{1, -3}, Vec{1, -6}, Vec{2, -9}}, {Vec{1, -3}, Vec{1, -6}, Vec{2, -9}},
{Vec{1, 2}, Vec{-1, -2}, Vec{}}, {Vec{1, 2}, Vec{-1, -2}, Vec{}},
} { } {
got := test.v1.Add(test.v2) got := Add(test.v1, test.v2)
if got != test.want { if got != test.want {
t.Errorf( t.Errorf(
"error: %v + %v: got=%v, want=%v", "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, -3}, Vec{1, -6}, Vec{0, 3}},
{Vec{1, 2}, Vec{1, 2}, Vec{}}, {Vec{1, 2}, Vec{1, 2}, Vec{}},
} { } {
got := test.v1.Sub(test.v2) got := Sub(test.v1, test.v2)
if got != test.want { if got != test.want {
t.Errorf( t.Errorf(
"error: %v - %v: got=%v, want=%v", "error: %v - %v: got=%v, want=%v",
@@ -67,7 +67,7 @@ func TestScale(t *testing.T) {
{2, Vec{1, -3}, Vec{2, -6}}, {2, Vec{1, -3}, Vec{2, -6}},
{10, Vec{1, 2}, Vec{10, 20}}, {10, Vec{1, 2}, Vec{10, 20}},
} { } {
got := test.v.Scale(test.a) got := Scale(test.a, test.v)
if got != test.want { if got != test.want {
t.Errorf( t.Errorf(
"error: %v * %v: got=%v, want=%v", "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}, {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 { if got != test.want {
t.Errorf( t.Errorf(
"error: %v · %v: got=%v, want=%v", "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 { if got != test.want {
t.Errorf( t.Errorf(
"error: %v · %v: got=%v, want=%v", "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{-4, 5}, 13},
{Vec{1, 2}, Vec{2, 3}, -1}, {Vec{1, 2}, Vec{2, 3}, -1},
} { } {
got := test.v1.Cross(test.v2) got := Cross(test.v1, test.v2)
if got != test.want { if got != test.want {
t.Errorf( t.Errorf(
"error: %v × %v = %v, want %v", "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{1, 1}, math.Pi, Vec{0, 0}},
{Vec{2, 2}, Vec{2, 0}, math.Pi, Vec{2, -2}}, {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) { if !vecApproxEqual(got, test.want) {
t.Errorf( t.Errorf(
"rotate(%v, %v, %v)= %v, want=%v", "rotate(%v, %v, %v)= %v, want=%v",