github.com/gopherd/gonum@v0.0.4/graph/product/product_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 product
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"testing"
    11  
    12  	"math/rand"
    13  
    14  	"github.com/gopherd/gonum/graph"
    15  	"github.com/gopherd/gonum/graph/encoding/dot"
    16  	"github.com/gopherd/gonum/graph/graphs/gen"
    17  	"github.com/gopherd/gonum/graph/simple"
    18  )
    19  
    20  func (n Node) DOTID() string { return fmt.Sprintf("(%d,%d)", n.A.ID(), n.B.ID()) }
    21  
    22  func left() *simple.UndirectedGraph {
    23  	edges := []simple.Edge{
    24  		{F: simple.Node(-1), T: simple.Node(-2)},
    25  		{F: simple.Node(-2), T: simple.Node(-3)},
    26  		{F: simple.Node(-2), T: simple.Node(-4)},
    27  		{F: simple.Node(-3), T: simple.Node(-5)},
    28  		{F: simple.Node(-4), T: simple.Node(-5)},
    29  	}
    30  	g := simple.NewUndirectedGraph()
    31  	for _, e := range edges {
    32  		g.SetEdge(e)
    33  	}
    34  	return g
    35  }
    36  
    37  func right() *simple.UndirectedGraph {
    38  	edges := []simple.Edge{
    39  		{F: simple.Node(1), T: simple.Node(2)},
    40  		{F: simple.Node(2), T: simple.Node(3)},
    41  		{F: simple.Node(2), T: simple.Node(4)},
    42  	}
    43  	g := simple.NewUndirectedGraph()
    44  	for _, e := range edges {
    45  		g.SetEdge(e)
    46  	}
    47  	return g
    48  }
    49  
    50  func path(m int) *simple.UndirectedGraph {
    51  	sign := 1
    52  	if m < 0 {
    53  		sign = -1
    54  		m = -m
    55  	}
    56  	g := simple.NewUndirectedGraph()
    57  	if m == 0 {
    58  		g.AddNode(simple.Node(0))
    59  	}
    60  	for i := 1; i <= m; i++ {
    61  		g.SetEdge(simple.Edge{F: simple.Node(sign * i), T: simple.Node(sign * (i + 1))})
    62  	}
    63  	return g
    64  }
    65  
    66  var productTests = []struct {
    67  	name string
    68  	a, b *simple.UndirectedGraph
    69  }{
    70  	{name: "paths", a: path(-1), b: path(1)},
    71  	{name: "wp_mp", a: path(-2), b: path(2)},
    72  	{name: "wp_gp", a: left(), b: right()},
    73  	{name: "gnp_2×2", a: gnp(2, 0.5, rand.NewSource(1)), b: gnp(2, 0.5, rand.NewSource(2))},
    74  	{name: "gnp_2×3", a: gnp(2, 0.5, rand.NewSource(1)), b: gnp(3, 0.5, rand.NewSource(2))},
    75  	{name: "gnp_3×3", a: gnp(3, 0.5, rand.NewSource(1)), b: gnp(3, 0.5, rand.NewSource(2))},
    76  	{name: "gnp_4×4", a: gnp(4, 0.5, rand.NewSource(1)), b: gnp(4, 0.5, rand.NewSource(2))},
    77  }
    78  
    79  func TestCartesian(t *testing.T) {
    80  	for _, test := range productTests {
    81  		got := simple.NewUndirectedGraph()
    82  		Cartesian(got, test.a, test.b)
    83  		gotBytes, _ := dot.Marshal(got, "", "", "  ")
    84  
    85  		want := simple.NewUndirectedGraph()
    86  		naiveCartesian(want, test.a, test.b)
    87  		wantBytes, _ := dot.Marshal(want, "", "", "  ")
    88  
    89  		gotEdgesLen := len(graph.EdgesOf(got.Edges()))
    90  		nA := len(graph.NodesOf(test.a.Nodes()))
    91  		mA := len(graph.EdgesOf(test.a.Edges()))
    92  		nB := len(graph.NodesOf(test.b.Nodes()))
    93  		mB := len(graph.EdgesOf(test.b.Edges()))
    94  		wantEdgesLen := mB*nA + mA*nB
    95  		if gotEdgesLen != wantEdgesLen {
    96  			t.Errorf("unexpected number of edges for Cartesian product of %s: got:%d want:%d",
    97  				test.name, gotEdgesLen, wantEdgesLen)
    98  		}
    99  
   100  		if !bytes.Equal(gotBytes, wantBytes) {
   101  			t.Errorf("unexpected Cartesian product result for %s:\ngot:\n%s\nwant:\n%s",
   102  				test.name, gotBytes, wantBytes)
   103  		}
   104  	}
   105  }
   106  
   107  // (u₁=v₁ and u₂~v₂) or (u₁~v₁ and u₂=v₂).
   108  func naiveCartesian(dst graph.Builder, a, b graph.Graph) {
   109  	_, _, product := cartesianNodes(a, b)
   110  
   111  	for _, p := range product {
   112  		dst.AddNode(p)
   113  	}
   114  
   115  	for _, u := range product {
   116  		for _, v := range product {
   117  			edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
   118  			edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
   119  			if (u.A.ID() == v.A.ID() && edgeInB) || (edgeInA && u.B.ID() == v.B.ID()) {
   120  				dst.SetEdge(dst.NewEdge(u, v))
   121  			}
   122  		}
   123  	}
   124  }
   125  
   126  func TestTensor(t *testing.T) {
   127  	for _, test := range productTests {
   128  		got := simple.NewUndirectedGraph()
   129  		Tensor(got, test.a, test.b)
   130  		gotBytes, _ := dot.Marshal(got, "", "", "  ")
   131  
   132  		want := simple.NewUndirectedGraph()
   133  		naiveTensor(want, test.a, test.b)
   134  		wantBytes, _ := dot.Marshal(want, "", "", "  ")
   135  
   136  		gotEdgesLen := len(graph.EdgesOf(got.Edges()))
   137  		mA := len(graph.EdgesOf(test.a.Edges()))
   138  		mB := len(graph.EdgesOf(test.b.Edges()))
   139  		wantEdgesLen := 2 * mA * mB
   140  		if gotEdgesLen != wantEdgesLen {
   141  			t.Errorf("unexpected number of edges for Tensor product of %s: got:%d want:%d",
   142  				test.name, gotEdgesLen, wantEdgesLen)
   143  		}
   144  
   145  		if !bytes.Equal(gotBytes, wantBytes) {
   146  			t.Errorf("unexpected Tensor product result for %s:\ngot:\n%s\nwant:\n%s",
   147  				test.name, gotBytes, wantBytes)
   148  		}
   149  	}
   150  }
   151  
   152  // u₁~v₁ and u₂~v₂.
   153  func naiveTensor(dst graph.Builder, a, b graph.Graph) {
   154  	_, _, product := cartesianNodes(a, b)
   155  
   156  	for _, p := range product {
   157  		dst.AddNode(p)
   158  	}
   159  
   160  	for _, u := range product {
   161  		for _, v := range product {
   162  			edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
   163  			edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
   164  			if edgeInA && edgeInB {
   165  				dst.SetEdge(dst.NewEdge(u, v))
   166  			}
   167  		}
   168  	}
   169  }
   170  
   171  func TestLexicographical(t *testing.T) {
   172  	for _, test := range productTests {
   173  		got := simple.NewUndirectedGraph()
   174  		Lexicographical(got, test.a, test.b)
   175  		gotBytes, _ := dot.Marshal(got, "", "", "  ")
   176  
   177  		want := simple.NewUndirectedGraph()
   178  		naiveLexicographical(want, test.a, test.b)
   179  		wantBytes, _ := dot.Marshal(want, "", "", "  ")
   180  
   181  		gotEdgesLen := len(graph.EdgesOf(got.Edges()))
   182  		nA := len(graph.NodesOf(test.a.Nodes()))
   183  		mA := len(graph.EdgesOf(test.a.Edges()))
   184  		nB := len(graph.NodesOf(test.b.Nodes()))
   185  		mB := len(graph.EdgesOf(test.b.Edges()))
   186  		wantEdgesLen := mB*nA + mA*nB*nB
   187  		if gotEdgesLen != wantEdgesLen {
   188  			t.Errorf("unexpected number of edges for Lexicographical product of %s: got:%d want:%d",
   189  				test.name, gotEdgesLen, wantEdgesLen)
   190  		}
   191  
   192  		if !bytes.Equal(gotBytes, wantBytes) {
   193  			t.Errorf("unexpected Lexicographical product result for %s:\ngot:\n%s\nwant:\n%s",
   194  				test.name, gotBytes, wantBytes)
   195  		}
   196  	}
   197  }
   198  
   199  // u₁~v₁ or (u₁=v₁ and u₂~v₂).
   200  func naiveLexicographical(dst graph.Builder, a, b graph.Graph) {
   201  	_, _, product := cartesianNodes(a, b)
   202  
   203  	for _, p := range product {
   204  		dst.AddNode(p)
   205  	}
   206  
   207  	for _, u := range product {
   208  		for _, v := range product {
   209  			edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
   210  			edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
   211  			if edgeInA || (u.A.ID() == v.A.ID() && edgeInB) {
   212  				dst.SetEdge(dst.NewEdge(u, v))
   213  			}
   214  		}
   215  	}
   216  }
   217  
   218  func TestStrong(t *testing.T) {
   219  	for _, test := range productTests {
   220  		got := simple.NewUndirectedGraph()
   221  		Strong(got, test.a, test.b)
   222  		gotBytes, _ := dot.Marshal(got, "", "", "  ")
   223  
   224  		want := simple.NewUndirectedGraph()
   225  		naiveStrong(want, test.a, test.b)
   226  		wantBytes, _ := dot.Marshal(want, "", "", "  ")
   227  
   228  		gotEdgesLen := len(graph.EdgesOf(got.Edges()))
   229  		nA := len(graph.NodesOf(test.a.Nodes()))
   230  		mA := len(graph.EdgesOf(test.a.Edges()))
   231  		nB := len(graph.NodesOf(test.b.Nodes()))
   232  		mB := len(graph.EdgesOf(test.b.Edges()))
   233  		wantEdgesLen := nA*mB + nB*mA + 2*mA*mB
   234  		if gotEdgesLen != wantEdgesLen {
   235  			t.Errorf("unexpected number of edges for Strong product of %s: got:%d want:%d",
   236  				test.name, gotEdgesLen, wantEdgesLen)
   237  		}
   238  
   239  		if !bytes.Equal(gotBytes, wantBytes) {
   240  			t.Errorf("unexpected Strong product result for %s:\ngot:\n%s\nwant:\n%s",
   241  				test.name, gotBytes, wantBytes)
   242  		}
   243  	}
   244  }
   245  
   246  // (u₁=v₁ and u₂~v₂) or (u₁~v₁ and u₂=v₂) or (u₁~v₁ and u₂~v₂).
   247  func naiveStrong(dst graph.Builder, a, b graph.Graph) {
   248  	_, _, product := cartesianNodes(a, b)
   249  
   250  	for _, p := range product {
   251  		dst.AddNode(p)
   252  	}
   253  
   254  	for _, u := range product {
   255  		for _, v := range product {
   256  			edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
   257  			edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
   258  			if (u.A.ID() == v.A.ID() && edgeInB) || (edgeInA && u.B.ID() == v.B.ID()) || (edgeInA && edgeInB) {
   259  				dst.SetEdge(dst.NewEdge(u, v))
   260  			}
   261  		}
   262  	}
   263  }
   264  
   265  func TestCoNormal(t *testing.T) {
   266  	for _, test := range productTests {
   267  		got := simple.NewUndirectedGraph()
   268  		CoNormal(got, test.a, test.b)
   269  		gotBytes, _ := dot.Marshal(got, "", "", "  ")
   270  
   271  		want := simple.NewUndirectedGraph()
   272  		naiveCoNormal(want, test.a, test.b)
   273  		wantBytes, _ := dot.Marshal(want, "", "", "  ")
   274  
   275  		if !bytes.Equal(gotBytes, wantBytes) {
   276  			t.Errorf("unexpected Co-normal product result for %s:\ngot:\n%s\nwant:\n%s",
   277  				test.name, gotBytes, wantBytes)
   278  		}
   279  	}
   280  }
   281  
   282  // u₁~v₁ or u₂~v₂.
   283  func naiveCoNormal(dst graph.Builder, a, b graph.Graph) {
   284  	_, _, product := cartesianNodes(a, b)
   285  
   286  	for _, p := range product {
   287  		dst.AddNode(p)
   288  	}
   289  
   290  	for _, u := range product {
   291  		for _, v := range product {
   292  			edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
   293  			edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
   294  			if edgeInA || edgeInB {
   295  				dst.SetEdge(dst.NewEdge(u, v))
   296  			}
   297  		}
   298  	}
   299  }
   300  
   301  func TestModular(t *testing.T) {
   302  	for _, test := range productTests {
   303  		got := simple.NewUndirectedGraph()
   304  		Modular(got, test.a, test.b)
   305  		gotBytes, _ := dot.Marshal(got, "", "", "  ")
   306  
   307  		want := simple.NewUndirectedGraph()
   308  		naiveModular(want, test.a, test.b)
   309  		wantBytes, _ := dot.Marshal(want, "", "", "  ")
   310  
   311  		if !bytes.Equal(gotBytes, wantBytes) {
   312  			t.Errorf("unexpected Modular product result for %s:\ngot:\n%s\nwant:\n%s", test.name, gotBytes, wantBytes)
   313  		}
   314  	}
   315  }
   316  
   317  func TestModularExt(t *testing.T) {
   318  	for _, test := range productTests {
   319  		got := simple.NewUndirectedGraph()
   320  		ModularExt(got, test.a, test.b, func(a, b graph.Edge) bool { return true })
   321  		gotBytes, _ := dot.Marshal(got, "", "", "  ")
   322  
   323  		want := simple.NewUndirectedGraph()
   324  		naiveModular(want, test.a, test.b)
   325  		wantBytes, _ := dot.Marshal(want, "", "", "  ")
   326  
   327  		if !bytes.Equal(gotBytes, wantBytes) {
   328  			t.Errorf("unexpected ModularExt product result for %s:\ngot:\n%s\nwant:\n%s", test.name, gotBytes, wantBytes)
   329  		}
   330  	}
   331  }
   332  
   333  // (u₁~v₁ and u₂~v₂) or (u₁≁v₁ and u₂≁v₂).
   334  func naiveModular(dst graph.Builder, a, b graph.Graph) {
   335  	_, _, product := cartesianNodes(a, b)
   336  
   337  	for _, p := range product {
   338  		dst.AddNode(p)
   339  	}
   340  
   341  	for i, u := range product {
   342  		for j, v := range product {
   343  			if i == j || u.A.ID() == v.A.ID() || u.B.ID() == v.B.ID() {
   344  				// No self-edges.
   345  				continue
   346  			}
   347  			edgeInA := a.Edge(u.A.ID(), v.A.ID()) != nil
   348  			edgeInB := b.Edge(u.B.ID(), v.B.ID()) != nil
   349  			if (edgeInA && edgeInB) || (!edgeInA && !edgeInB) {
   350  				dst.SetEdge(dst.NewEdge(u, v))
   351  			}
   352  		}
   353  	}
   354  }
   355  
   356  func BenchmarkProduct(b *testing.B) {
   357  	for seed, bench := range []struct {
   358  		name    string
   359  		product func(dst graph.Builder, a, b graph.Graph)
   360  		len     []int
   361  	}{
   362  		{"Cartesian", Cartesian, []int{50, 100}},
   363  		{"Cartesian naive", naiveCartesian, []int{50, 100}},
   364  		{"CoNormal", CoNormal, []int{50}},
   365  		{"CoNormal naive", naiveCoNormal, []int{50}},
   366  		{"Lexicographical", Lexicographical, []int{50}},
   367  		{"Lexicographical naive", naiveLexicographical, []int{50}},
   368  		{"Modular", Modular, []int{50}},
   369  		{"Modular naive", naiveModular, []int{50}},
   370  		{"Strong", Strong, []int{50}},
   371  		{"Strong naive", naiveStrong, []int{50}},
   372  		{"Tensor", Tensor, []int{50}},
   373  		{"Tensor naive", naiveTensor, []int{50}},
   374  	} {
   375  		for _, p := range []float64{0.05, 0.25, 0.5, 0.75, 0.95} {
   376  			for _, n := range bench.len {
   377  				src := rand.NewSource(uint64(seed))
   378  				b.Run(fmt.Sprintf("%s %d-%.2f", bench.name, n, p), func(b *testing.B) {
   379  					g1 := gnp(n, p, src)
   380  					g2 := gnp(n, p, src)
   381  					b.ResetTimer()
   382  					for i := 0; i < b.N; i++ {
   383  						dst := simple.NewDirectedGraph()
   384  						bench.product(dst, g1, g2)
   385  					}
   386  				})
   387  			}
   388  		}
   389  	}
   390  }
   391  
   392  func gnp(n int, p float64, src rand.Source) *simple.UndirectedGraph {
   393  	g := simple.NewUndirectedGraph()
   394  	err := gen.Gnp(g, n, p, src)
   395  	if err != nil {
   396  		panic(fmt.Sprintf("gnp: bad test: %v", err))
   397  	}
   398  	return g
   399  }