blob: 81683fabe48f71da9ade8b7dff392c33e606e21d [file] [log] [blame]
// Copyright ©2019 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 product
import (
"bytes"
"fmt"
"testing"
"golang.org/x/exp/rand"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding/dot"
"gonum.org/v1/gonum/graph/graphs/gen"
"gonum.org/v1/gonum/graph/simple"
)
func (n Node) DOTID() string { return fmt.Sprintf("(%d,%d)", n.A.ID(), n.B.ID()) }
func left() *simple.UndirectedGraph {
edges := []simple.Edge{
{F: simple.Node(-1), T: simple.Node(-2)},
{F: simple.Node(-2), T: simple.Node(-3)},
{F: simple.Node(-2), T: simple.Node(-4)},
{F: simple.Node(-3), T: simple.Node(-5)},
{F: simple.Node(-4), T: simple.Node(-5)},
}
g := simple.NewUndirectedGraph()
for _, e := range edges {
g.SetEdge(e)
}
return g
}
func right() *simple.UndirectedGraph {
edges := []simple.Edge{
{F: simple.Node(1), T: simple.Node(2)},
{F: simple.Node(2), T: simple.Node(3)},
{F: simple.Node(2), T: simple.Node(4)},
}
g := simple.NewUndirectedGraph()
for _, e := range edges {
g.SetEdge(e)
}
return g
}
func path(m int) *simple.UndirectedGraph {
sign := 1
if m < 0 {
sign = -1
m = -m
}
g := simple.NewUndirectedGraph()
if m == 0 {
g.AddNode(simple.Node(0))
}
for i := 1; i <= m; i++ {
g.SetEdge(simple.Edge{F: simple.Node(sign * i), T: simple.Node(sign * (i + 1))})
}
return g
}
var productTests = []struct {
name string
a, b *simple.UndirectedGraph
}{
{name: "paths", a: path(-1), b: path(1)},
{name: "wp_mp", a: path(-2), b: path(2)},
{name: "wp_gp", a: left(), b: right()},
{name: "gnp_2×2", a: gnp(2, 0.5, rand.NewSource(1)), b: gnp(2, 0.5, rand.NewSource(2))},
{name: "gnp_2×3", a: gnp(2, 0.5, rand.NewSource(1)), b: gnp(3, 0.5, rand.NewSource(2))},
{name: "gnp_3×3", a: gnp(3, 0.5, rand.NewSource(1)), b: gnp(3, 0.5, rand.NewSource(2))},
{name: "gnp_4×4", a: gnp(4, 0.5, rand.NewSource(1)), b: gnp(4, 0.5, rand.NewSource(2))},
}
func TestCartesian(t *testing.T) {
for _, test := range productTests {
got := simple.NewUndirectedGraph()
Cartesian(got, test.a, test.b)
gotBytes, _ := dot.Marshal(got, "", "", " ")
want := simple.NewUndirectedGraph()
naiveCartesian(want, test.a, test.b)
wantBytes, _ := dot.Marshal(want, "", "", " ")
gotEdgesLen := len(graph.EdgesOf(got.Edges()))
nA := len(graph.NodesOf(test.a.Nodes()))
mA := len(graph.EdgesOf(test.a.Edges()))
nB := len(graph.NodesOf(test.b.Nodes()))
mB := len(graph.EdgesOf(test.b.Edges()))
wantEdgesLen := mB*nA + mA*nB
if gotEdgesLen != wantEdgesLen {
t.Errorf("unexpected number of edges for Cartesian product of %s: got:%d want:%d",
test.name, gotEdgesLen, wantEdgesLen)
}
if !bytes.Equal(gotBytes, wantBytes) {
t.Errorf("unexpected Cartesian product result for %s:\ngot:\n%s\nwant:\n%s",
test.name, gotBytes, wantBytes)
}
}
}
// (u₁=v₁ and u₂~v₂) or (u₁~v₁ and u₂=v₂).
func naiveCartesian(dst graph.Builder, a, b graph.Graph) {
_, _, product := cartesianNodes(a, b)
for _, p := range product {
dst.AddNode(p)
}
for _, u := range product {
for _, v := range product {
edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
if (u.A.ID() == v.A.ID() && edgeInB) || (edgeInA && u.B.ID() == v.B.ID()) {
dst.SetEdge(dst.NewEdge(u, v))
}
}
}
}
func TestTensor(t *testing.T) {
for _, test := range productTests {
got := simple.NewUndirectedGraph()
Tensor(got, test.a, test.b)
gotBytes, _ := dot.Marshal(got, "", "", " ")
want := simple.NewUndirectedGraph()
naiveTensor(want, test.a, test.b)
wantBytes, _ := dot.Marshal(want, "", "", " ")
gotEdgesLen := len(graph.EdgesOf(got.Edges()))
mA := len(graph.EdgesOf(test.a.Edges()))
mB := len(graph.EdgesOf(test.b.Edges()))
wantEdgesLen := 2 * mA * mB
if gotEdgesLen != wantEdgesLen {
t.Errorf("unexpected number of edges for Tensor product of %s: got:%d want:%d",
test.name, gotEdgesLen, wantEdgesLen)
}
if !bytes.Equal(gotBytes, wantBytes) {
t.Errorf("unexpected Tensor product result for %s:\ngot:\n%s\nwant:\n%s",
test.name, gotBytes, wantBytes)
}
}
}
// u₁~v₁ and u₂~v₂.
func naiveTensor(dst graph.Builder, a, b graph.Graph) {
_, _, product := cartesianNodes(a, b)
for _, p := range product {
dst.AddNode(p)
}
for _, u := range product {
for _, v := range product {
edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
if edgeInA && edgeInB {
dst.SetEdge(dst.NewEdge(u, v))
}
}
}
}
func TestLexicographical(t *testing.T) {
for _, test := range productTests {
got := simple.NewUndirectedGraph()
Lexicographical(got, test.a, test.b)
gotBytes, _ := dot.Marshal(got, "", "", " ")
want := simple.NewUndirectedGraph()
naiveLexicographical(want, test.a, test.b)
wantBytes, _ := dot.Marshal(want, "", "", " ")
gotEdgesLen := len(graph.EdgesOf(got.Edges()))
nA := len(graph.NodesOf(test.a.Nodes()))
mA := len(graph.EdgesOf(test.a.Edges()))
nB := len(graph.NodesOf(test.b.Nodes()))
mB := len(graph.EdgesOf(test.b.Edges()))
wantEdgesLen := mB*nA + mA*nB*nB
if gotEdgesLen != wantEdgesLen {
t.Errorf("unexpected number of edges for Lexicographical product of %s: got:%d want:%d",
test.name, gotEdgesLen, wantEdgesLen)
}
if !bytes.Equal(gotBytes, wantBytes) {
t.Errorf("unexpected Lexicographical product result for %s:\ngot:\n%s\nwant:\n%s",
test.name, gotBytes, wantBytes)
}
}
}
// u₁~v₁ or (u₁=v₁ and u₂~v₂).
func naiveLexicographical(dst graph.Builder, a, b graph.Graph) {
_, _, product := cartesianNodes(a, b)
for _, p := range product {
dst.AddNode(p)
}
for _, u := range product {
for _, v := range product {
edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
if edgeInA || (u.A.ID() == v.A.ID() && edgeInB) {
dst.SetEdge(dst.NewEdge(u, v))
}
}
}
}
func TestStrong(t *testing.T) {
for _, test := range productTests {
got := simple.NewUndirectedGraph()
Strong(got, test.a, test.b)
gotBytes, _ := dot.Marshal(got, "", "", " ")
want := simple.NewUndirectedGraph()
naiveStrong(want, test.a, test.b)
wantBytes, _ := dot.Marshal(want, "", "", " ")
gotEdgesLen := len(graph.EdgesOf(got.Edges()))
nA := len(graph.NodesOf(test.a.Nodes()))
mA := len(graph.EdgesOf(test.a.Edges()))
nB := len(graph.NodesOf(test.b.Nodes()))
mB := len(graph.EdgesOf(test.b.Edges()))
wantEdgesLen := nA*mB + nB*mA + 2*mA*mB
if gotEdgesLen != wantEdgesLen {
t.Errorf("unexpected number of edges for Strong product of %s: got:%d want:%d",
test.name, gotEdgesLen, wantEdgesLen)
}
if !bytes.Equal(gotBytes, wantBytes) {
t.Errorf("unexpected Strong product result for %s:\ngot:\n%s\nwant:\n%s",
test.name, gotBytes, wantBytes)
}
}
}
// (u₁=v₁ and u₂~v₂) or (u₁~v₁ and u₂=v₂) or (u₁~v₁ and u₂~v₂).
func naiveStrong(dst graph.Builder, a, b graph.Graph) {
_, _, product := cartesianNodes(a, b)
for _, p := range product {
dst.AddNode(p)
}
for _, u := range product {
for _, v := range product {
edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
if (u.A.ID() == v.A.ID() && edgeInB) || (edgeInA && u.B.ID() == v.B.ID()) || (edgeInA && edgeInB) {
dst.SetEdge(dst.NewEdge(u, v))
}
}
}
}
func TestCoNormal(t *testing.T) {
for _, test := range productTests {
got := simple.NewUndirectedGraph()
CoNormal(got, test.a, test.b)
gotBytes, _ := dot.Marshal(got, "", "", " ")
want := simple.NewUndirectedGraph()
naiveCoNormal(want, test.a, test.b)
wantBytes, _ := dot.Marshal(want, "", "", " ")
if !bytes.Equal(gotBytes, wantBytes) {
t.Errorf("unexpected Co-normal product result for %s:\ngot:\n%s\nwant:\n%s",
test.name, gotBytes, wantBytes)
}
}
}
// u₁~v₁ or u₂~v₂.
func naiveCoNormal(dst graph.Builder, a, b graph.Graph) {
_, _, product := cartesianNodes(a, b)
for _, p := range product {
dst.AddNode(p)
}
for _, u := range product {
for _, v := range product {
edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
if edgeInA || edgeInB {
dst.SetEdge(dst.NewEdge(u, v))
}
}
}
}
func TestModular(t *testing.T) {
for _, test := range productTests {
got := simple.NewUndirectedGraph()
Modular(got, test.a, test.b)
gotBytes, _ := dot.Marshal(got, "", "", " ")
want := simple.NewUndirectedGraph()
naiveModular(want, test.a, test.b)
wantBytes, _ := dot.Marshal(want, "", "", " ")
if !bytes.Equal(gotBytes, wantBytes) {
t.Errorf("unexpected Modular product result for %s:\ngot:\n%s\nwant:\n%s", test.name, gotBytes, wantBytes)
}
}
}
func TestModularExt(t *testing.T) {
for _, test := range productTests {
got := simple.NewUndirectedGraph()
ModularExt(got, test.a, test.b, func(a, b graph.Edge) bool { return true })
gotBytes, _ := dot.Marshal(got, "", "", " ")
want := simple.NewUndirectedGraph()
naiveModular(want, test.a, test.b)
wantBytes, _ := dot.Marshal(want, "", "", " ")
if !bytes.Equal(gotBytes, wantBytes) {
t.Errorf("unexpected ModularExt product result for %s:\ngot:\n%s\nwant:\n%s", test.name, gotBytes, wantBytes)
}
}
}
// (u₁~v₁ and u₂~v₂) or (u₁≁v₁ and u₂≁v₂).
func naiveModular(dst graph.Builder, a, b graph.Graph) {
_, _, product := cartesianNodes(a, b)
for _, p := range product {
dst.AddNode(p)
}
for i, u := range product {
for j, v := range product {
if i == j || u.A.ID() == v.A.ID() || u.B.ID() == v.B.ID() {
// No self-edges.
continue
}
edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
if (edgeInA && edgeInB) || (!edgeInA && !edgeInB) {
dst.SetEdge(dst.NewEdge(u, v))
}
}
}
}
func BenchmarkProduct(b *testing.B) {
for seed, bench := range []struct {
name string
product func(dst graph.Builder, a, b graph.Graph)
len []int
}{
{"Cartesian", Cartesian, []int{50, 100}},
{"Cartesian naive", naiveCartesian, []int{50, 100}},
{"CoNormal", CoNormal, []int{50}},
{"CoNormal naive", naiveCoNormal, []int{50}},
{"Lexicographical", Lexicographical, []int{50}},
{"Lexicographical naive", naiveLexicographical, []int{50}},
{"Modular", Modular, []int{50}},
{"Modular naive", naiveModular, []int{50}},
{"Strong", Strong, []int{50}},
{"Strong naive", naiveStrong, []int{50}},
{"Tensor", Tensor, []int{50}},
{"Tensor naive", naiveTensor, []int{50}},
} {
for _, p := range []float64{0.05, 0.25, 0.5, 0.75, 0.95} {
for _, n := range bench.len {
src := rand.NewSource(uint64(seed))
b.Run(fmt.Sprintf("%s %d-%.2f", bench.name, n, p), func(b *testing.B) {
g1 := gnp(n, p, src)
g2 := gnp(n, p, src)
b.ResetTimer()
for i := 0; i < b.N; i++ {
dst := simple.NewDirectedGraph()
bench.product(dst, g1, g2)
}
})
}
}
}
}
func gnp(n int, p float64, src rand.Source) *simple.UndirectedGraph {
g := simple.NewUndirectedGraph()
err := gen.Gnp(g, n, p, src)
if err != nil {
panic(fmt.Sprintf("gnp: bad test: %v", err))
}
return g
}