gonum.org/v1/gonum@v0.14.0/graph/topo/paton_cycles_test.go (about)

     1  // Copyright ©2015 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 topo
     6  
     7  import (
     8  	"reflect"
     9  	"testing"
    10  
    11  	"gonum.org/v1/gonum/graph"
    12  	"gonum.org/v1/gonum/graph/internal/ordered"
    13  	"gonum.org/v1/gonum/graph/simple"
    14  )
    15  
    16  var undirectedCyclesInTests = []struct {
    17  	g    []intset
    18  	want [][][]int64
    19  }{
    20  	{
    21  		g: []intset{
    22  			0:  linksTo(1, 2),
    23  			1:  linksTo(2, 4, 5, 9),
    24  			2:  linksTo(4, 7, 9),
    25  			3:  linksTo(5),
    26  			4:  linksTo(8),
    27  			5:  linksTo(7, 8),
    28  			6:  nil,
    29  			7:  nil,
    30  			8:  nil,
    31  			9:  nil,
    32  			10: linksTo(11, 12),
    33  			11: linksTo(12),
    34  			12: nil,
    35  		},
    36  		want: [][][]int64{
    37  			{
    38  				{0, 1, 2, 0},
    39  				{1, 2, 7, 5, 1},
    40  				{1, 2, 9, 1},
    41  				{1, 4, 8, 5, 1},
    42  				{2, 4, 8, 5, 7, 2},
    43  				{10, 11, 12, 10},
    44  			},
    45  			{
    46  				{0, 1, 2, 0},
    47  				{1, 2, 4, 1},
    48  				{1, 2, 7, 5, 1},
    49  				{1, 2, 9, 1},
    50  				{1, 4, 8, 5, 1},
    51  				{10, 11, 12, 10},
    52  			},
    53  			{
    54  				{0, 1, 2, 0},
    55  				{1, 2, 4, 1},
    56  				{1, 2, 9, 1},
    57  				{1, 4, 8, 5, 1},
    58  				{2, 4, 8, 5, 7, 2},
    59  				{10, 11, 12, 10},
    60  			},
    61  			{
    62  				{0, 1, 2, 0},
    63  				{1, 2, 4, 1},
    64  				{1, 2, 7, 5, 1},
    65  				{1, 2, 9, 1},
    66  				{2, 4, 8, 5, 7, 2},
    67  				{10, 11, 12, 10},
    68  			},
    69  		},
    70  	},
    71  }
    72  
    73  func TestUndirectedCyclesIn(t *testing.T) {
    74  	for i, test := range undirectedCyclesInTests {
    75  		g := simple.NewUndirectedGraph()
    76  		g.AddNode(simple.Node(-10)) // Make sure we test graphs with sparse IDs.
    77  		for u, e := range test.g {
    78  			// Add nodes that are not defined by an edge.
    79  			if g.Node(int64(u)) == nil {
    80  				g.AddNode(simple.Node(u))
    81  			}
    82  			for v := range e {
    83  				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
    84  			}
    85  		}
    86  		cycles := UndirectedCyclesIn(g)
    87  		var got [][]int64
    88  		if cycles != nil {
    89  			got = make([][]int64, len(cycles))
    90  		}
    91  		// Canonicalise the cycles.
    92  		for j, c := range cycles {
    93  			ids := make([]int64, len(c))
    94  			for k, n := range canonicalise(c[:len(c)-1]) {
    95  				ids[k] = n.ID()
    96  			}
    97  			ids[len(ids)-1] = ids[0]
    98  			got[j] = ids
    99  		}
   100  		ordered.BySliceValues(got)
   101  		var matched bool
   102  		for _, want := range test.want {
   103  			if reflect.DeepEqual(got, want) {
   104  				matched = true
   105  				break
   106  			}
   107  		}
   108  		if !matched {
   109  			t.Errorf("unexpected paton result for %d:\n\tgot:%#v\n\twant from:%#v", i, got, test.want)
   110  		}
   111  	}
   112  }
   113  
   114  // canonicalise returns the cycle path c cyclicly permuted such that
   115  // the first element has the lowest ID and then conditionally
   116  // reversed so that the second element has the lowest possible
   117  // neighbouring ID.
   118  // c lists each node only once - the final node must not be a
   119  // reiteration of the first node.
   120  func canonicalise(c []graph.Node) []graph.Node {
   121  	if len(c) < 2 {
   122  		return c
   123  	}
   124  	idx := 0
   125  	min := c[0].ID()
   126  	for i, n := range c[1:] {
   127  		if id := n.ID(); id < min {
   128  			idx = i + 1
   129  			min = id
   130  		}
   131  	}
   132  	if idx != 0 {
   133  		c = append(c[idx:], c[:idx]...)
   134  	}
   135  	if c[len(c)-1].ID() < c[1].ID() {
   136  		ordered.Reverse(c[1:])
   137  	}
   138  	return c
   139  }