// Copyright ©2015 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 graph_test import ( "math" "testing" "gonum.org/v1/gonum/graph" "gonum.org/v1/gonum/graph/iterator" "gonum.org/v1/gonum/graph/simple" "gonum.org/v1/gonum/mat" ) type weightedDirectedBuilder interface { graph.WeightedBuilder graph.WeightedDirected } var weightedDirectedGraphs = []struct { skipUnweighted bool g func() weightedDirectedBuilder edges []simple.WeightedEdge absent float64 merge func(x, y float64, xe, ye graph.Edge) float64 want mat.Matrix }{ { g: func() weightedDirectedBuilder { return simple.NewWeightedDirectedGraph(0, 0) }, edges: []simple.WeightedEdge{ {F: simple.Node(0), T: simple.Node(1), W: 2}, {F: simple.Node(1), T: simple.Node(0), W: 1}, {F: simple.Node(1), T: simple.Node(2), W: 1}, }, want: mat.NewSymDense(3, []float64{ 0, (1. + 2.) / 2., 0, (1. + 2.) / 2., 0, 1. / 2., 0, 1. / 2., 0, }), }, { g: func() weightedDirectedBuilder { return simple.NewWeightedDirectedGraph(0, 0) }, edges: []simple.WeightedEdge{ {F: simple.Node(0), T: simple.Node(1), W: 2}, {F: simple.Node(1), T: simple.Node(0), W: 1}, {F: simple.Node(1), T: simple.Node(2), W: 1}, }, absent: 1, merge: func(x, y float64, _, _ graph.Edge) float64 { return math.Sqrt(x * y) }, want: mat.NewSymDense(3, []float64{ 0, math.Sqrt(1 * 2), 0, math.Sqrt(1 * 2), 0, math.Sqrt(1 * 1), 0, math.Sqrt(1 * 1), 0, }), }, { skipUnweighted: true, // The min merge function cannot be used in the unweighted case. g: func() weightedDirectedBuilder { return simple.NewWeightedDirectedGraph(0, 0) }, edges: []simple.WeightedEdge{ {F: simple.Node(0), T: simple.Node(1), W: 2}, {F: simple.Node(1), T: simple.Node(0), W: 1}, {F: simple.Node(1), T: simple.Node(2), W: 1}, }, merge: func(x, y float64, _, _ graph.Edge) float64 { return math.Min(x, y) }, want: mat.NewSymDense(3, []float64{ 0, math.Min(1, 2), 0, math.Min(1, 2), 0, math.Min(1, 0), 0, math.Min(1, 0), 0, }), }, { g: func() weightedDirectedBuilder { return simple.NewWeightedDirectedGraph(0, 0) }, edges: []simple.WeightedEdge{ {F: simple.Node(0), T: simple.Node(1), W: 2}, {F: simple.Node(1), T: simple.Node(0), W: 1}, {F: simple.Node(1), T: simple.Node(2), W: 1}, }, merge: func(x, y float64, xe, ye graph.Edge) float64 { if xe == nil { return y } if ye == nil { return x } return math.Min(x, y) }, want: mat.NewSymDense(3, []float64{ 0, math.Min(1, 2), 0, math.Min(1, 2), 0, 1, 0, 1, 0, }), }, { g: func() weightedDirectedBuilder { return simple.NewWeightedDirectedGraph(0, 0) }, edges: []simple.WeightedEdge{ {F: simple.Node(0), T: simple.Node(1), W: 2}, {F: simple.Node(1), T: simple.Node(0), W: 1}, {F: simple.Node(1), T: simple.Node(2), W: 1}, }, merge: func(x, y float64, _, _ graph.Edge) float64 { return math.Max(x, y) }, want: mat.NewSymDense(3, []float64{ 0, math.Max(1, 2), 0, math.Max(1, 2), 0, math.Max(1, 0), 0, math.Max(1, 0), 0, }), }, } func TestUndirect(t *testing.T) { for i, test := range weightedDirectedGraphs { if test.skipUnweighted { continue } g := test.g() for _, e := range test.edges { g.SetWeightedEdge(e) } src := graph.Undirect{G: g} nodes := graph.NodesOf(src.Nodes()) dst := simple.NewUndirectedMatrixFrom(nodes, 0, 0, 0) for _, u := range nodes { for _, v := range graph.NodesOf(src.From(u.ID())) { dst.SetEdge(src.Edge(u.ID(), v.ID())) } } want := unit{test.want} if !mat.Equal(dst.Matrix(), want) { t.Errorf("unexpected result for case %d:\ngot:\n%.4v\nwant:\n%.4v", i, mat.Formatted(dst.Matrix()), mat.Formatted(want), ) } } } func TestUndirectWeighted(t *testing.T) { for i, test := range weightedDirectedGraphs { g := test.g() for _, e := range test.edges { g.SetWeightedEdge(e) } src := graph.UndirectWeighted{G: g, Absent: test.absent, Merge: test.merge} nodes := graph.NodesOf(src.Nodes()) dst := simple.NewUndirectedMatrixFrom(nodes, 0, 0, 0) for _, u := range nodes { for _, v := range graph.NodesOf(src.From(u.ID())) { dst.SetWeightedEdge(src.WeightedEdge(u.ID(), v.ID())) } } if !mat.Equal(dst.Matrix(), test.want) { t.Errorf("unexpected result for case %d:\ngot:\n%.4v\nwant:\n%.4v", i, mat.Formatted(dst.Matrix()), mat.Formatted(test.want), ) } } } type unit struct { mat.Matrix } func (m unit) At(i, j int) float64 { v := m.Matrix.At(i, j) if v == 0 { return 0 } return 1 } var nodeIteratorPairTests = []struct { a, b graph.Nodes len int }{ {a: graph.Empty, b: graph.Empty, len: 0}, {a: iterator.NewOrderedNodes(nil), b: iterator.NewOrderedNodes(nil), len: 0}, {a: iterator.NewOrderedNodes([]graph.Node{simple.Node(0)}), b: graph.Empty, len: 1}, {a: graph.Empty, b: iterator.NewOrderedNodes([]graph.Node{simple.Node(0)}), len: 1}, {a: iterator.NewOrderedNodes([]graph.Node{simple.Node(0)}), b: iterator.NewOrderedNodes([]graph.Node{simple.Node(0)}), len: 1}, {a: iterator.NewOrderedNodes([]graph.Node{simple.Node(0)}), b: iterator.NewOrderedNodes([]graph.Node{simple.Node(1)}), len: 2}, {a: iterator.NewOrderedNodes([]graph.Node{simple.Node(0), simple.Node(1)}), b: iterator.NewOrderedNodes([]graph.Node{simple.Node(1)}), len: 2}, {a: iterator.NewOrderedNodes([]graph.Node{simple.Node(1)}), b: iterator.NewOrderedNodes([]graph.Node{simple.Node(0), simple.Node(1)}), len: 2}, } func TestNodeIteratorPair(t *testing.T) { for _, test := range nodeIteratorPairTests { it := graph.NewNodeIteratorPair(test.a, test.b) for i := 0; i < 2; i++ { n := it.Len() if n != test.len { t.Errorf("unexpected length of iterator construction/reset: got:%d want:%d", n, test.len) } for it.Next() { n-- } if n != 0 { t.Errorf("unexpected remaining nodes after iterator completion: got:%d want:0", n) } it.Reset() } } }