gonum.org/v1/gonum@v0.14.0/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 "golang.org/x/exp/rand" 13 14 "gonum.org/v1/gonum/graph" 15 "gonum.org/v1/gonum/graph/encoding/dot" 16 "gonum.org/v1/gonum/graph/graphs/gen" 17 "gonum.org/v1/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 }