// 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 gen import ( "fmt" "gonum.org/v1/gonum/graph" ) // GraphBuilder is a graph that can have nodes and edges added. type GraphBuilder interface { HasEdgeBetween(xid, yid int64) bool graph.Builder } func abs(a int) int { if a < 0 { return -a } return a } // NodeIDGraphBuild is a graph that can create new nodes with // specified IDs. type NodeIDGraphBuilder interface { graph.Builder graph.NodeWithIDer } // IDer is a mapping from an index to a node ID. type IDer interface { // Len returns the length of the set of node IDs. Len() int // ID returns the ID of the indexed node. // ID must be a bijective function. No check // is made for this property. ID(int) int64 } // IDRange is an IDer that provides a set of IDs in [First, Last]. type IDRange struct{ First, Last int64 } func (r IDRange) Len() int { return int(r.Last - r.First + 1) } func (r IDRange) ID(i int) int64 { return r.First + int64(i) } // IDSet is an IDer providing an explicit set of IDs. type IDSet []int64 func (s IDSet) Len() int { return len(s) } func (s IDSet) ID(i int) int64 { return s[i] } // Complete constructs a complete graph in dst using nodes with the given IDs. // If any ID appears twice in ids, Complete will panic. func Complete(dst NodeIDGraphBuilder, ids IDer) { switch ids.Len() { case 0: return case 1: u, new := dst.NodeWithID(ids.ID(0)) if new { dst.AddNode(u) } return } for i := 0; i < ids.Len(); i++ { uid := ids.ID(i) u, _ := dst.NodeWithID(uid) for j := i + 1; j < ids.Len(); j++ { vid := ids.ID(j) if uid == vid { panic(fmt.Errorf("gen: node ID collision i=%d j=%d: id=%d", i, j, uid)) } v, _ := dst.NodeWithID(vid) dst.SetEdge(dst.NewEdge(u, v)) } } } // Cycle constructs a cycle in dst using the node IDs in cycle. // If dst is a directed graph, edges are directed from earlier nodes to later // nodes in cycle. If any ID appears twice in cycle, Cycle will panic. func Cycle(dst NodeIDGraphBuilder, cycle IDer) { switch cycle.Len() { case 0: return case 1: u, new := dst.NodeWithID(cycle.ID(0)) if new { dst.AddNode(u) } return } err := check(cycle) if err != nil { panic(err) } cycleNoCheck(dst, cycle) } func cycleNoCheck(dst NodeIDGraphBuilder, cycle IDer) { for i := 0; i < cycle.Len(); i++ { uid := cycle.ID(i) vid := cycle.ID((i + 1) % cycle.Len()) u, _ := dst.NodeWithID(uid) v, _ := dst.NodeWithID(vid) dst.SetEdge(dst.NewEdge(u, v)) } } // Path constructs a path graph in dst with // If dst is a directed graph, edges are directed from earlier nodes to later // nodes in path. If any ID appears twice in path, Path will panic. func Path(dst NodeIDGraphBuilder, path IDer) { switch path.Len() { case 0: return case 1: u, new := dst.NodeWithID(path.ID(0)) if new { dst.AddNode(u) } return } err := check(path) if err != nil { panic(err) } for i := 0; i < path.Len()-1; i++ { uid := path.ID(i) vid := path.ID(i + 1) u, _ := dst.NodeWithID(uid) v, _ := dst.NodeWithID(vid) dst.SetEdge(dst.NewEdge(u, v)) } } // Star constructs a star graph in dst with edges between the center node ID to // node with IDs specified in leaves. // If dst is a directed graph, edges are directed from the center node to the // leaves. If any ID appears twice in leaves and center, Star will panic. func Star(dst NodeIDGraphBuilder, center int64, leaves IDer) { c, new := dst.NodeWithID(center) if new { dst.AddNode(c) } if leaves.Len() == 0 { return } err := check(leaves, center) if err != nil { panic(err) } for i := 0; i < leaves.Len(); i++ { id := leaves.ID(i) n, _ := dst.NodeWithID(id) dst.SetEdge(dst.NewEdge(c, n)) } } // Wheel constructs a wheel graph in dst with edges from the center // node ID to node with IDs specified in cycle and between nodes with IDs // adjacent in the cycle. // If dst is a directed graph, edges are directed from the center node to the // cycle and from earlier nodes to later nodes in cycle. If any ID appears // twice in cycle and center, Wheel will panic. func Wheel(dst NodeIDGraphBuilder, center int64, cycle IDer) { Star(dst, center, cycle) if cycle.Len() <= 1 { return } cycleNoCheck(dst, cycle) } // Tree constructs an n-ary tree breadth-first in dst with the given fan-out, n. // If the number of nodes does not equal \sum_{i=0}^h n^i, where h is an integer // indicating the height of the tree, a partial tree will be constructed with not // all nodes having zero or n children, and not all leaves being h from the root. // If the number of nodes is greater than one, n must be non-zero and // less than the number of nodes, otherwise Tree will panic. // If dst is a directed graph, edges are directed from the root to the leaves. // If any ID appears more than once in nodes, Tree will panic. func Tree(dst NodeIDGraphBuilder, n int, nodes IDer) { switch nodes.Len() { case 0: return case 1: if u, new := dst.NodeWithID(nodes.ID(0)); new { dst.AddNode(u) } return } if n < 1 || nodes.Len() <= n { panic("gen: invalid fan-out") } err := check(nodes) if err != nil { panic(err) } j := 0 for i := 0; j < nodes.Len(); i++ { u, _ := dst.NodeWithID(nodes.ID(i)) for j = n*i + 1; j <= n*i+n && j < nodes.Len(); j++ { v, _ := dst.NodeWithID(nodes.ID(j)) dst.SetEdge(dst.NewEdge(u, v)) } } } // check confirms that no node ID exists more than once in ids and extra. func check(ids IDer, extra ...int64) error { seen := make(map[int64]int, ids.Len()+len(extra)) for j := 0; j < ids.Len(); j++ { uid := ids.ID(j) if prev, exists := seen[uid]; exists { return fmt.Errorf("gen: node ID collision i=%d j=%d: id=%d", prev, j, uid) } seen[uid] = j } lenIDs := ids.Len() for j, uid := range extra { if prev, exists := seen[uid]; exists { if prev < lenIDs { if len(extra) == 1 { return fmt.Errorf("gen: node ID collision i=%d with extra: id=%d", prev, uid) } return fmt.Errorf("gen: node ID collision i=%d with extra j=%d: id=%d", prev, j, uid) } prev -= lenIDs return fmt.Errorf("gen: extra node ID collision i=%d j=%d: id=%d", prev, j, uid) } seen[uid] = j + lenIDs } return nil }