diff --git a/glide.lock b/glide.lock index 2931fee1b6af..386ebbc6f0eb 100644 --- a/glide.lock +++ b/glide.lock @@ -477,14 +477,13 @@ imports: - name: github.com/gonum/floats version: f74b330d45c56584a6ea7a27f5c64ea2900631e9 - name: github.com/gonum/graph - version: bde6d0fbd9dec5a997e906611fe0364001364c41 + version: 50b27dea7ebbfb052dfaf91681afc6fde28d8796 subpackages: - - concrete - encoding/dot - internal - path + - simple - topo - - traverse - name: github.com/gonum/internal version: e57e4534cf9b3b00ef6c0175f59d8d2d34f60914 subpackages: diff --git a/glide.yaml b/glide.yaml index 7c0c32a3834f..186c570f854d 100644 --- a/glide.yaml +++ b/glide.yaml @@ -118,7 +118,7 @@ import: version: f74b330d45c56584a6ea7a27f5c64ea2900631e9 # cli - package: github.com/gonum/graph - version: bde6d0fbd9dec5a997e906611fe0364001364c41 + version: 50b27dea7ebbfb052dfaf91681afc6fde28d8796 # cli - package: github.com/gonum/internal version: e57e4534cf9b3b00ef6c0175f59d8d2d34f60914 diff --git a/pkg/cmd/server/bootstrappolicy/policy.go b/pkg/cmd/server/bootstrappolicy/policy.go index 086698ff3167..2e37bc83f7b7 100644 --- a/pkg/cmd/server/bootstrappolicy/policy.go +++ b/pkg/cmd/server/bootstrappolicy/policy.go @@ -505,7 +505,7 @@ func GetOpenshiftBootstrapClusterRoles() []rbac.ClusterRole { rbac.NewRule("get", "list").Groups(appsGroup, extensionsGroup).Resources("replicasets").RuleOrDie(), rbac.NewRule("delete").Groups(imageGroup, legacyImageGroup).Resources("images").RuleOrDie(), - rbac.NewRule("get", "list").Groups(imageGroup, legacyImageGroup).Resources("images", "imagestreams").RuleOrDie(), + rbac.NewRule("get", "list", "watch").Groups(imageGroup, legacyImageGroup).Resources("images", "imagestreams").RuleOrDie(), rbac.NewRule("update").Groups(imageGroup, legacyImageGroup).Resources("imagestreams/status").RuleOrDie(), }, }, diff --git a/pkg/oc/admin/prune/imageprune/helper.go b/pkg/oc/admin/prune/imageprune/helper.go index 2a707446cdcb..2b59b0dae19e 100644 --- a/pkg/oc/admin/prune/imageprune/helper.go +++ b/pkg/oc/admin/prune/imageprune/helper.go @@ -7,12 +7,15 @@ import ( "sort" "strings" - kapi "k8s.io/kubernetes/pkg/apis/core" - "github.com/docker/distribution/registry/api/errcode" "github.com/golang/glog" + kmeta "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" kerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/kubernetes/pkg/api/legacyscheme" + kapiref "k8s.io/kubernetes/pkg/api/ref" + kapi "k8s.io/kubernetes/pkg/apis/core" imageapi "github.com/openshift/origin/pkg/image/apis/image" "github.com/openshift/origin/pkg/util/netutils" @@ -265,3 +268,36 @@ func (e *ErrBadReference) String() string { } return fmt.Sprintf("%s[%s]: invalid %s reference %q: %s", e.kind, name, targetKind, e.reference, e.reason) } + +func getName(obj runtime.Object) string { + accessor, err := kmeta.Accessor(obj) + if err != nil { + glog.V(4).Infof("Error getting accessor for %#v", obj) + return "" + } + ns := accessor.GetNamespace() + if len(ns) == 0 { + return accessor.GetName() + } + return fmt.Sprintf("%s/%s", ns, accessor.GetName()) +} + +func getKindName(obj *kapi.ObjectReference) string { + if obj == nil { + return "unknown object" + } + name := obj.Name + if len(obj.Namespace) > 0 { + name = obj.Namespace + "/" + name + } + return fmt.Sprintf("%s[%s]", obj.Kind, name) +} + +func getRef(obj runtime.Object) *kapi.ObjectReference { + ref, err := kapiref.GetReference(legacyscheme.Scheme, obj) + if err != nil { + glog.Errorf("failed to get reference to object %T: %v", obj, err) + return nil + } + return ref +} diff --git a/pkg/oc/admin/prune/imageprune/prune.go b/pkg/oc/admin/prune/imageprune/prune.go index 302696c599be..3724a43a9c50 100644 --- a/pkg/oc/admin/prune/imageprune/prune.go +++ b/pkg/oc/admin/prune/imageprune/prune.go @@ -2,10 +2,12 @@ package imageprune import ( "encoding/json" + "errors" "fmt" "net/http" "net/url" "reflect" + "sort" "strings" "time" @@ -15,15 +17,13 @@ import ( gonum "github.com/gonum/graph" kerrapi "k8s.io/apimachinery/pkg/api/errors" - kmeta "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" kerrors "k8s.io/apimachinery/pkg/util/errors" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/util/retry" - "k8s.io/kubernetes/pkg/api/legacyscheme" - kapiref "k8s.io/kubernetes/pkg/api/ref" kapi "k8s.io/kubernetes/pkg/apis/core" kapisext "k8s.io/kubernetes/pkg/apis/extensions" @@ -56,8 +56,25 @@ const ( // ReferencedImageLayerEdgeKind defines an edge from an ImageStreamNode or an // ImageNode to an ImageComponentNode. ReferencedImageLayerEdgeKind = "ReferencedImageLayer" + + // ReferencedImageManifestEdgeKind defines an edge from an ImageStreamNode or an + // ImageNode to an ImageComponentNode. + ReferencedImageManifestEdgeKind = "ReferencedImageManifest" + + defaultPruneImageWorkerCount = 5 ) +// RegistryClientFactoryFunc is a factory function returning a registry client for use in a worker. +type RegistryClientFactoryFunc func() (*http.Client, error) + +//ImagePrunerFactoryFunc is a factory function returning an image deleter for use in a worker. +type ImagePrunerFactoryFunc func() (ImageDeleter, error) + +// FakeRegistryClientFactory is a registry client factory creating no client at all. Useful for dry run. +func FakeRegistryClientFactory() (*http.Client, error) { + return nil, nil +} + // pruneAlgorithm contains the various settings to use when evaluating images // and layers for pruning. type pruneAlgorithm struct { @@ -130,9 +147,13 @@ type PrunerOptions struct { // Images is the entire list of images in OpenShift. An image must be in this // list to be a candidate for pruning. Images *imageapi.ImageList + // ImageWatcher watches for image changes. + ImageWatcher watch.Interface // Streams is the entire list of image streams across all namespaces in the // cluster. Streams *imageapi.ImageStreamList + // StreamWatcher watches for stream changes. + StreamWatcher watch.Interface // Pods is the entire list of pods across all namespaces in the cluster. Pods *kapi.PodList // RCs is the entire list of replication controllers across all namespaces in @@ -157,29 +178,46 @@ type PrunerOptions struct { // will be removed. DryRun bool // RegistryClient is the http.Client to use when contacting the registry. - RegistryClient *http.Client + RegistryClientFactory RegistryClientFactoryFunc // RegistryURL is the URL of the integrated Docker registry. RegistryURL *url.URL // IgnoreInvalidRefs indicates that all invalid references should be ignored. IgnoreInvalidRefs bool + // NumWorkers is a desired number of workers concurrently handling image prune jobs. If less than 1, the + // default number of workers will be spawned. + NumWorkers int } -// Pruner knows how to prune istags, images, layers and image configs. +// Pruner knows how to prune istags, images, manifest, layers, image configs and blobs. type Pruner interface { // Prune uses imagePruner, streamPruner, layerLinkPruner, blobPruner, and // manifestPruner to remove images that have been identified as candidates // for pruning based on the Pruner's internal pruning algorithm. // Please see NewPruner for details on the algorithm. - Prune(imagePruner ImageDeleter, streamPruner ImageStreamDeleter, layerLinkPruner LayerLinkDeleter, blobPruner BlobDeleter, manifestPruner ManifestDeleter) error + Prune( + imagePrunerFactory ImagePrunerFactoryFunc, + streamPruner ImageStreamDeleter, + layerLinkPruner LayerLinkDeleter, + blobPruner BlobDeleter, + manifestPruner ManifestDeleter, + ) (deletions []Deletion, failures []Failure) } // pruner is an object that knows how to prune a data set type pruner struct { - g genericgraph.Graph - algorithm pruneAlgorithm - registryClient *http.Client - registryURL *url.URL - ignoreInvalidRefs bool + g genericgraph.Graph + algorithm pruneAlgorithm + ignoreInvalidRefs bool + registryClientFactory RegistryClientFactoryFunc + registryURL *url.URL + imageWatcher watch.Interface + imageStreamWatcher watch.Interface + imageStreamLimits map[string][]*kapi.LimitRange + // sorted queue of images to prune; nil stands for empty queue + queue *nodeItem + // contains prunable images removed from queue that are currently being processed + processedImages map[*imagegraph.ImageNode]*Job + numWorkers int } var _ Pruner = &pruner{} @@ -256,10 +294,19 @@ func NewPruner(options PrunerOptions) (Pruner, kerrors.Aggregate) { algorithm.namespace = options.Namespace p := &pruner{ - algorithm: algorithm, - registryClient: options.RegistryClient, - registryURL: options.RegistryURL, - ignoreInvalidRefs: options.IgnoreInvalidRefs, + algorithm: algorithm, + ignoreInvalidRefs: options.IgnoreInvalidRefs, + registryClientFactory: options.RegistryClientFactory, + registryURL: options.RegistryURL, + processedImages: make(map[*imagegraph.ImageNode]*Job), + imageWatcher: options.ImageWatcher, + imageStreamWatcher: options.StreamWatcher, + imageStreamLimits: options.LimitRanges, + numWorkers: options.NumWorkers, + } + + if p.numWorkers < 1 { + p.numWorkers = defaultPruneImageWorkerCount } if err := p.buildGraph(options); err != nil { @@ -296,10 +343,7 @@ func getValue(option interface{}) string { return "" } -// addImagesToGraph adds all images to the graph that belong to one of the -// registries in the algorithm and are at least as old as the minimum age -// threshold as specified by the algorithm. It also adds all the images' layers -// to the graph. +// addImagesToGraph adds all images, their manifests and their layers to the graph. func (p *pruner) addImagesToGraph(images *imageapi.ImageList) []error { for i := range images.Items { image := &images.Items[i] @@ -319,6 +363,10 @@ func (p *pruner) addImagesToGraph(images *imageapi.ImageList) []error { layerNode := imagegraph.EnsureImageComponentLayerNode(p.g, layer.Name) p.g.AddEdge(imageNode, layerNode, ReferencedImageLayerEdgeKind) } + + glog.V(4).Infof("Adding image manifest %q to graph", image.Name) + manifestNode := imagegraph.EnsureImageComponentManifestNode(p.g, image.Name) + p.g.AddEdge(imageNode, manifestNode, ReferencedImageManifestEdgeKind) } return nil @@ -402,10 +450,15 @@ func (p *pruner) addImageStreamsToGraph(streams *imageapi.ImageStreamList, limit } glog.V(4).Infof("Adding reference from stream %s to %s", getName(stream), cn.Describe()) - if cn.Type == imagegraph.ImageComponentTypeConfig { + switch cn.Type { + case imagegraph.ImageComponentTypeConfig: p.g.AddEdge(imageStreamNode, s, ReferencedImageConfigEdgeKind) - } else { + case imagegraph.ImageComponentTypeLayer: p.g.AddEdge(imageStreamNode, s, ReferencedImageLayerEdgeKind) + case imagegraph.ImageComponentTypeManifest: + p.g.AddEdge(imageStreamNode, s, ReferencedImageManifestEdgeKind) + default: + utilruntime.HandleError(fmt.Errorf("internal error: unhandled image component type %q", cn.Type)) } } } @@ -772,6 +825,88 @@ func (p *pruner) addBuildStrategyImageReferencesToGraph(referrer *kapi.ObjectRef return nil } +func (p *pruner) handleImageStreamEvent(event watch.Event) { + getIsNode := func() (*imageapi.ImageStream, *imagegraph.ImageStreamNode) { + is, ok := event.Object.(*imageapi.ImageStream) + if !ok { + utilruntime.HandleError(fmt.Errorf("internal error: expected ImageStream object in %s event, not %T", event.Type, event.Object)) + return nil, nil + } + n := p.g.Find(imagegraph.ImageStreamNodeName(is)) + if isNode, ok := n.(*imagegraph.ImageStreamNode); ok { + return is, isNode + } + return is, nil + } + + // NOTE: an addition of an imagestream previously deleted from the graph is a noop due to a limitation of + // the current gonum/graph package + switch event.Type { + case watch.Added: + is, isNode := getIsNode() + if is == nil { + return + } + if isNode != nil { + glog.V(4).Infof("Ignoring added ImageStream %s that is already present in the graph", getName(is)) + return + } + glog.V(4).Infof("Adding ImageStream %s to the graph", getName(is)) + p.addImageStreamsToGraph(&imageapi.ImageStreamList{Items: []imageapi.ImageStream{*is}}, p.imageStreamLimits) + + case watch.Modified: + is, isNode := getIsNode() + if is == nil { + return + } + + if isNode != nil { + glog.V(4).Infof("Removing updated ImageStream %s from the graph", getName(is)) + // first remove the current node if present + p.g.RemoveNode(isNode) + } + + glog.V(4).Infof("Adding updated ImageStream %s back to the graph", getName(is)) + p.addImageStreamsToGraph(&imageapi.ImageStreamList{Items: []imageapi.ImageStream{*is}}, p.imageStreamLimits) + } +} + +func (p *pruner) handleImageEvent(event watch.Event) { + getImageNode := func() (*imageapi.Image, *imagegraph.ImageNode) { + img, ok := event.Object.(*imageapi.Image) + if !ok { + utilruntime.HandleError(fmt.Errorf("internal error: expected Image object in %s event, not %T", event.Type, event.Object)) + return nil, nil + } + return img, imagegraph.FindImage(p.g, img.Name) + } + + switch event.Type { + // NOTE: an addition of an image previously deleted from the graph is a noop due to a limitation of the + // current gonum/graph package + case watch.Added: + img, imgNode := getImageNode() + if img == nil { + return + } + if imgNode != nil { + glog.V(4).Infof("Ignoring added Image %s that is already present in the graph", img) + return + } + glog.V(4).Infof("Adding new Image %s to the graph", img.Name) + p.addImagesToGraph(&imageapi.ImageList{Items: []imageapi.Image{*img}}) + + case watch.Deleted: + img, imgNode := getImageNode() + if imgNode == nil { + glog.V(4).Infof("Ignoring event for deleted Image %s that is not present in the graph", img.Name) + return + } + glog.V(4).Infof("Removing deleted image %s from the graph", img.Name) + p.g.RemoveNode(imgNode) + } +} + // getImageNodes returns only nodes of type ImageNode. func getImageNodes(nodes []gonum.Node) map[string]*imagegraph.ImageNode { ret := make(map[string]*imagegraph.ImageNode) @@ -820,83 +955,40 @@ func imageIsPrunable(g genericgraph.Graph, imageNode *imagegraph.ImageNode, algo return true } -// calculatePrunableImages returns the list of prunable images and a -// graph.NodeSet containing the image node IDs. func calculatePrunableImages( g genericgraph.Graph, imageNodes map[string]*imagegraph.ImageNode, algorithm pruneAlgorithm, -) (map[string]*imagegraph.ImageNode, genericgraph.NodeSet) { - prunable := make(map[string]*imagegraph.ImageNode) - ids := make(genericgraph.NodeSet) +) []*imagegraph.ImageNode { + prunable := []*imagegraph.ImageNode{} for _, imageNode := range imageNodes { glog.V(4).Infof("Examining image %q", imageNode.Image.Name) if imageIsPrunable(g, imageNode, algorithm) { glog.V(4).Infof("Image %q is prunable", imageNode.Image.Name) - prunable[imageNode.Image.Name] = imageNode - ids.Add(imageNode.ID()) - } - } - - return prunable, ids -} - -// subgraphWithoutPrunableImages creates a subgraph from g with prunable image -// nodes excluded. -func subgraphWithoutPrunableImages(g genericgraph.Graph, prunableImageIDs genericgraph.NodeSet) genericgraph.Graph { - return g.Subgraph( - func(g genericgraph.Interface, node gonum.Node) bool { - return !prunableImageIDs.Has(node.ID()) - }, - func(g genericgraph.Interface, from, to gonum.Node, edgeKinds sets.String) bool { - if prunableImageIDs.Has(from.ID()) { - return false - } - if prunableImageIDs.Has(to.ID()) { - return false - } - return true - }, - ) -} - -// calculatePrunableImageComponents returns the list of prunable image components. -func calculatePrunableImageComponents(g genericgraph.Graph) []*imagegraph.ImageComponentNode { - components := []*imagegraph.ImageComponentNode{} - nodes := g.Nodes() - - for i := range nodes { - cn, ok := nodes[i].(*imagegraph.ImageComponentNode) - if !ok { - continue - } - - glog.V(4).Infof("Examining %v", cn) - if imageComponentIsPrunable(g, cn) { - glog.V(4).Infof("%v is prunable", cn) - components = append(components, cn) + prunable = append(prunable, imageNode) } } - return components + return prunable } -func getPrunableComponents(g genericgraph.Graph, prunableImageIDs genericgraph.NodeSet) []*imagegraph.ImageComponentNode { - graphWithoutPrunableImages := subgraphWithoutPrunableImages(g, prunableImageIDs) - return calculatePrunableImageComponents(graphWithoutPrunableImages) -} - -// pruneStreams removes references from all image streams' status.tags entries -// to prunable images, invoking streamPruner.UpdateImageStream for each updated -// stream. +// pruneStreams removes references from all image streams' status.tags entries to prunable images, invoking +// streamPruner.UpdateImageStream for each updated stream. func pruneStreams( g genericgraph.Graph, - prunableImageNodes map[string]*imagegraph.ImageNode, + prunableImageNodes []*imagegraph.ImageNode, streamPruner ImageStreamDeleter, keepYoungerThan time.Time, -) error { +) (deletions []Deletion, failures []Failure) { + imageNameToNode := map[string]*imagegraph.ImageNode{} + for _, node := range prunableImageNodes { + imageNameToNode[node.Image.Name] = node + } + + noChangeErr := errors.New("nothing changed") + glog.V(4).Infof("Removing pruned image references from streams") for _, node := range g.Nodes() { streamNode, ok := node.(*imagegraph.ImageStreamNode) @@ -909,7 +1001,7 @@ func pruneStreams( if err != nil { if kerrapi.IsNotFound(err) { glog.V(4).Infof("Unable to get image stream %s: removed during prune", streamName) - return nil + return noChangeErr } return err } @@ -918,7 +1010,7 @@ func pruneStreams( deletedTags := sets.NewString() for tag := range stream.Status.Tags { - if updated, deleted := pruneISTagHistory(g, prunableImageNodes, keepYoungerThan, streamName, stream, tag); deleted { + if updated, deleted := pruneISTagHistory(g, imageNameToNode, keepYoungerThan, streamName, stream, tag); deleted { deletedTags.Insert(tag) } else if updated { updatedTags.Insert(tag) @@ -926,7 +1018,7 @@ func pruneStreams( } if updatedTags.Len() == 0 && deletedTags.Len() == 0 { - return nil + return noChangeErr } updatedStream, err := streamPruner.UpdateImageStream(stream) @@ -943,13 +1035,42 @@ func pruneStreams( return err }) + if err == noChangeErr { + continue + } if err != nil { - return fmt.Errorf("unable to prune stream %s: %v", streamName, err) + failures = append(failures, Failure{Node: streamNode, Err: err}) + } else { + deletions = append(deletions, Deletion{Node: streamNode}) } } glog.V(4).Infof("Done removing pruned image references from streams") - return nil + return +} + +// strengthenReferencesFromFailedImageStreams turns weak references between image streams and images to +// strong. This must be called right after the image stream pruning to prevent images that failed to be +// untagged from being pruned. +func strengthenReferencesFromFailedImageStreams(g genericgraph.Graph, failures []Failure) { + for _, f := range failures { + for _, n := range g.From(f.Node) { + imageNode, ok := n.(*imagegraph.ImageNode) + if !ok { + continue + } + edge := g.Edge(f.Node, imageNode) + if edge == nil { + continue + } + kinds := g.EdgeKinds(edge) + if kinds.Has(ReferencedImageEdgeKind) { + continue + } + g.RemoveEdge(edge) + g.AddEdge(f.Node, imageNode, ReferencedImageEdgeKind) + } + } } // pruneISTagHistory processes tag event list of the given image stream tag. It removes references to images @@ -1011,170 +1132,436 @@ func tagEventIsPrunable( return false, "the tag event is younger than threshold" } -// pruneImages invokes imagePruner.DeleteImage with each image that is prunable. -func pruneImages(g genericgraph.Graph, imageNodes map[string]*imagegraph.ImageNode, imagePruner ImageDeleter) []error { - errs := []error{} +// byLayerCountAndAge sorts a list of image nodes from the largest (by the number of image layers) to the +// smallest. Images with the same number of layers are ordered from the oldest to the youngest. +type byLayerCountAndAge []*imagegraph.ImageNode - for _, imageNode := range imageNodes { - if err := imagePruner.DeleteImage(imageNode.Image); err != nil { - errs = append(errs, fmt.Errorf("error removing image %q: %v", imageNode.Image.Name, err)) +func (b byLayerCountAndAge) Len() int { return len(b) } +func (b byLayerCountAndAge) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b byLayerCountAndAge) Less(i, j int) bool { + fst, snd := b[i].Image, b[j].Image + if len(fst.DockerImageLayers) > len(snd.DockerImageLayers) { + return true + } + if len(fst.DockerImageLayers) < len(snd.DockerImageLayers) { + return false + } + + return fst.CreationTimestamp.Before(&snd.CreationTimestamp) || + (!snd.CreationTimestamp.Before(&fst.CreationTimestamp) && fst.Name < snd.Name) +} + +// nodeItem is an item of a doubly-linked list of image nodes. +type nodeItem struct { + node *imagegraph.ImageNode + prev, next *nodeItem +} + +// pop removes the item from a doubly-linked list and returns the image node it holds and its former next +// neighbour. +func (i *nodeItem) pop() (node *imagegraph.ImageNode, next *nodeItem) { + n, p := i.next, i.prev + if p != nil { + p.next = n + } + if n != nil { + n.prev = p + } + return i.node, n +} + +// insertAfter makes a new list item from the given node and inserts it into the list right after the given +// item. The newly created item is returned. +func insertAfter(item *nodeItem, node *imagegraph.ImageNode) *nodeItem { + newItem := &nodeItem{ + node: node, + prev: item, + } + if item != nil { + if item.next != nil { + item.next.prev = newItem + newItem.next = item.next } + item.next = newItem } + return newItem +} - return errs +// makeQueue makes a doubly-linked list of items out of the given array of image nodes. +func makeQueue(nodes []*imagegraph.ImageNode) *nodeItem { + var head, tail *nodeItem + for i, n := range nodes { + tail = insertAfter(tail, n) + if i == 0 { + head = tail + } + } + return head } -// Run identifies images eligible for pruning, invoking imagePruner for each image, and then it identifies -// image configs and layers eligible for pruning, invoking layerLinkPruner for each registry URL that has -// layers or configs that can be pruned. +// Prune prunes the objects like this: +// 1. it calculates the prunable images and builds a queue +// - the queue does not ever grow, it only shrinks (newly created images are not added) +// 2. it untags the prunable images from image streams +// 3. it spawns workers +// 4. it turns each prunable image into a job for the workers and makes sure they are busy +// 5. it terminates the workers once the queue is empty and reports results func (p *pruner) Prune( - imagePruner ImageDeleter, + imagePrunerFactory ImagePrunerFactoryFunc, streamPruner ImageStreamDeleter, layerLinkPruner LayerLinkDeleter, blobPruner BlobDeleter, manifestPruner ManifestDeleter, -) error { +) (deletions []Deletion, failures []Failure) { allNodes := p.g.Nodes() imageNodes := getImageNodes(allNodes) - if len(imageNodes) == 0 { - return nil + prunable := calculatePrunableImages(p.g, imageNodes, p.algorithm) + + /* Instead of deleting streams in a per-image job, prune them all at once. Otherwise each image stream + * would have to be modified for each prunable image it contains. */ + deletions, failures = pruneStreams(p.g, prunable, streamPruner, p.algorithm.keepYoungerThan) + /* if namespace is specified, prune only ImageStreams and nothing more if we have any errors after + * ImageStreams pruning this may mean that we still have references to images. */ + if len(p.algorithm.namespace) > 0 || len(prunable) == 0 { + return deletions, failures } - prunableImageNodes, prunableImageIDs := calculatePrunableImages(p.g, imageNodes, p.algorithm) + strengthenReferencesFromFailedImageStreams(p.g, failures) - err := pruneStreams(p.g, prunableImageNodes, streamPruner, p.algorithm.keepYoungerThan) - // if namespace is specified prune only ImageStreams and nothing more - // if we have any errors after ImageStreams pruning this may mean that - // we still have references to images. - if len(p.algorithm.namespace) > 0 || err != nil { - return err - } + // Sorting images from the largest (by number of layers) to the smallest is supposed to distribute the + // blob deletion workload equally across whole queue. + // If processed randomly, most probably, job processed in the beginning wouldn't delete any blobs (due to + // too many remaining referers) contrary to the jobs processed at the end. + // The assumption is based on another assumption that images with many layers have a low probability of + // sharing their components with other images. + sort.Sort(byLayerCountAndAge(prunable)) + p.queue = makeQueue(prunable) - var errs []error + var ( + jobChan = make(chan *Job) + resultChan = make(chan JobResult) + ) - if p.algorithm.pruneRegistry { - prunableComponents := getPrunableComponents(p.g, prunableImageIDs) - errs = append(errs, pruneImageComponents(p.g, p.registryClient, p.registryURL, prunableComponents, layerLinkPruner)...) - errs = append(errs, pruneBlobs(p.g, p.registryClient, p.registryURL, prunableComponents, blobPruner)...) - errs = append(errs, pruneManifests(p.g, p.registryClient, p.registryURL, prunableImageNodes, manifestPruner)...) - - if len(errs) > 0 { - // If we had any errors deleting layers, blobs, or manifest data from the registry, - // stop here and don't delete any images. This way, you can rerun prune and retry - // things that failed. - return kerrors.NewAggregate(errs) + defer close(jobChan) + + for i := 0; i < p.numWorkers; i++ { + worker, err := NewWorker( + p.algorithm, + p.registryClientFactory, + p.registryURL, + imagePrunerFactory, + streamPruner, + layerLinkPruner, + blobPruner, + manifestPruner, + ) + if err != nil { + failures = append(failures, Failure{ + Err: fmt.Errorf("failed to initialize worker: %v", err), + }) + return } + go worker.Run(jobChan, resultChan) } - errs = pruneImages(p.g, prunableImageNodes, imagePruner) - return kerrors.NewAggregate(errs) + ds, fs := p.runLoop(jobChan, resultChan) + deletions = append(deletions, ds...) + failures = append(failures, fs...) + + return } -// imageComponentIsPrunable returns true if the image component is not referenced by any images. -func imageComponentIsPrunable(g genericgraph.Graph, cn *imagegraph.ImageComponentNode) bool { - for _, predecessor := range g.To(cn) { - glog.V(4).Infof("Examining predecessor %#v of image config %v", predecessor, cn) - if g.Kind(predecessor) == imagegraph.ImageNodeKind { - glog.V(4).Infof("Config %v has an image predecessor", cn) - return false +// runLoop processes the queue of prunable images until empty. It makes the workers busy and updates the graph +// with each change. +func (p *pruner) runLoop( + jobChan chan<- *Job, + resultChan <-chan JobResult, +) (deletions []Deletion, failures []Failure) { + imgUpdateChan := p.imageWatcher.ResultChan() + isUpdateChan := p.imageStreamWatcher.ResultChan() + for { + // make workers busy + for len(p.processedImages) < p.numWorkers { + job, blocked := p.getNextJob() + if blocked { + break + } + if job == nil { + if len(p.processedImages) == 0 { + return + } + break + } + jobChan <- job + p.processedImages[job.Image] = job } - } - return true + select { + case res := <-resultChan: + p.updateGraphWithResult(&res) + for _, deletion := range res.Deletions { + deletions = append(deletions, deletion) + } + for _, failure := range res.Failures { + failures = append(failures, failure) + } + delete(p.processedImages, res.Job.Image) + case event := <-isUpdateChan: + p.handleImageStreamEvent(event) + case event := <-imgUpdateChan: + p.handleImageEvent(event) + } + } } -// streamReferencingImageComponent returns a list of ImageStreamNodes that reference a -// given ImageComponentNode. -func streamsReferencingImageComponent(g genericgraph.Graph, cn *imagegraph.ImageComponentNode) []*imagegraph.ImageStreamNode { - ret := []*imagegraph.ImageStreamNode{} - for _, predecessor := range g.To(cn) { - if g.Kind(predecessor) != imagegraph.ImageStreamNodeKind { +// getNextJob removes a prunable image from the queue, makes a job out of it and returns it. +// Image may be removed from the queue without being processed if it becomes not prunable (by being referred +// by a new image stream). Image may also be skipped and processed later when it is currently blocked. +// +// Image is blocked when at least one of its components is currently being processed in a running job and +// the component has either: +// - only one remaining strong reference from the blocked image (the other references are being currently +// removed) +// - only one remaining reference in an image stream, where the component is tagged (via image) (the other +// references are being currently removed) +// +// The concept of blocked images attempts to preserve image components until the very last image +// referencing them is deleted. Otherwise an image previously considered as prunable becomes not prunable may +// become not usable since its components have been removed already. +func (p *pruner) getNextJob() (job *Job, blocked bool) { + if p.queue == nil { + return + } + + pop := func(item *nodeItem) (*imagegraph.ImageNode, *nodeItem) { + node, next := item.pop() + if item == p.queue { + p.queue = next + } + return node, next + } + + for item := p.queue; item != nil; { + // something could have changed + if !imageIsPrunable(p.g, item.node, p.algorithm) { + _, item = pop(item) continue } - ret = append(ret, predecessor.(*imagegraph.ImageStreamNode)) + + if components, blocked := getImageComponents(p.g, p.processedImages, item.node); !blocked { + job = &Job{ + Image: item.node, + Components: components, + } + _, item = pop(item) + break + } + item = item.next } - return ret + blocked = job == nil && p.queue != nil + + return } -// pruneImageComponents invokes layerLinkDeleter.DeleteLayerLink for each repository layer link to -// be deleted from the registry. -func pruneImageComponents( - g genericgraph.Graph, - registryClient *http.Client, - registryURL *url.URL, - imageComponents []*imagegraph.ImageComponentNode, - layerLinkDeleter LayerLinkDeleter, -) []error { - errs := []error{} - - for _, cn := range imageComponents { - // get streams that reference config - streamNodes := streamsReferencingImageComponent(g, cn) - - for _, streamNode := range streamNodes { - streamName := getName(streamNode.ImageStream) - glog.V(4).Infof("Pruning repository %s/%s: %s", registryURL.Host, streamName, cn.Describe()) - if err := layerLinkDeleter.DeleteLayerLink(registryClient, registryURL, streamName, cn.Component); err != nil { - errs = append(errs, fmt.Errorf("error pruning layer link %s in the repository %s: %v", cn.Component, streamName, err)) +// updateGraphWithResult updates the graph with the result from completed job. Image nodes are deleted for +// each deleted image. Image components are deleted if they were removed from the global blob store. Unlinked +// imagecomponent (layer/config/manifest link) will cause an edge between image stream and the component to be +// deleted. +func (p *pruner) updateGraphWithResult(res *JobResult) { + imageDeleted := false + for _, d := range res.Deletions { + switch d.Node.(type) { + case *imagegraph.ImageNode: + imageDeleted = true + p.g.RemoveNode(d.Node) + case *imagegraph.ImageComponentNode: + // blob -> delete the node with all the edges + if d.Parent == nil { + p.g.RemoveNode(d.Node) + continue + } + + // link in a repository -> delete just edges + isn, ok := d.Parent.(*imagegraph.ImageStreamNode) + if !ok { + continue } + edge := p.g.Edge(isn, d.Node) + if edge == nil { + continue + } + p.g.RemoveEdge(edge) + case *imagegraph.ImageStreamNode: + // ignore + default: + utilruntime.HandleError(fmt.Errorf("internal error: unhandled graph node %t", d.Node)) } } - return errs + if imageDeleted { + return + } } -// pruneBlobs invokes blobPruner.DeleteBlob for each blob to be deleted from the -// registry. -func pruneBlobs( +// getImageComponents gathers image components with locations, where they can be removed at this time. +// Each component can be prunable in several image streams and in the global blob store. +func getImageComponents( g genericgraph.Graph, - registryClient *http.Client, - registryURL *url.URL, - componentNodes []*imagegraph.ImageComponentNode, - blobPruner BlobDeleter, -) []error { - errs := []error{} + processedImages map[*imagegraph.ImageNode]*Job, + image *imagegraph.ImageNode, +) (components ComponentRetentions, blocked bool) { + components = make(ComponentRetentions) + + for _, node := range g.From(image) { + kinds := g.EdgeKinds(g.Edge(image, node)) + if len(kinds.Intersection(sets.NewString( + ReferencedImageLayerEdgeKind, + ReferencedImageConfigEdgeKind, + ReferencedImageManifestEdgeKind, + ))) == 0 { + continue + } + + imageStrongRefCounter := 0 + imageMarkedForDeletionCounter := 0 + referencingStreams := map[*imagegraph.ImageStreamNode]struct{}{} + referencingImages := map[*imagegraph.ImageNode]struct{}{} + + comp, ok := node.(*imagegraph.ImageComponentNode) + if !ok { + continue + } + + for _, ref := range g.To(comp) { + switch t := ref.(type) { + case (*imagegraph.ImageNode): + imageStrongRefCounter++ + if _, processed := processedImages[t]; processed { + imageMarkedForDeletionCounter++ + } + referencingImages[t] = struct{}{} + + case *imagegraph.ImageStreamNode: + referencingStreams[t] = struct{}{} + + default: + continue + } + } + + switch { + // the component is referenced only by the given image -> prunable globally + case imageStrongRefCounter < 2: + components.Add(comp, true) + // the component can be pruned once the other referencing image that is being deleted is finished; + // don't touch it until then + case imageStrongRefCounter-imageMarkedForDeletionCounter < 2: + return nil, true + // not prunable component + default: + components.Add(comp, false) + } - for _, cn := range componentNodes { - if err := blobPruner.DeleteBlob(registryClient, registryURL, cn.Component); err != nil { - errs = append(errs, fmt.Errorf("error removing blob %s from the registry %s: %v", - cn.Component, registryURL.Host, err)) + if addComponentReferencingStreams( + g, + components, + referencingImages, + referencingStreams, + processedImages, + comp, + ) { + return nil, true } } - return errs + return } -// pruneManifests invokes manifestPruner.DeleteManifest for each repository -// manifest to be deleted from the registry. -func pruneManifests( +// addComponentReferencingStreams records information about prunability of the given component in all the +// streams referencing it (via tagged image). It updates given components attribute. +func addComponentReferencingStreams( g genericgraph.Graph, - registryClient *http.Client, - registryURL *url.URL, - imageNodes map[string]*imagegraph.ImageNode, - manifestPruner ManifestDeleter, -) []error { - errs := []error{} - - for _, imageNode := range imageNodes { - for _, n := range g.To(imageNode) { - streamNode, ok := n.(*imagegraph.ImageStreamNode) - if !ok { + components ComponentRetentions, + referencingImages map[*imagegraph.ImageNode]struct{}, + referencingStreams map[*imagegraph.ImageStreamNode]struct{}, + processedImages map[*imagegraph.ImageNode]*Job, + comp *imagegraph.ImageComponentNode, +) (blocked bool) { +streamLoop: + for stream := range referencingStreams { + refCounter := 0 + markedForDeletionCounter := 0 + + for image := range referencingImages { + edge := g.Edge(stream, image) + if edge == nil { + continue + } + kinds := g.EdgeKinds(edge) + // tagged not prunable image -> keep the component in the stream + if kinds.Has(ReferencedImageEdgeKind) { + components.AddReferencingStreams(comp, false, stream) + continue streamLoop + } + if !kinds.Has(WeakReferencedImageEdgeKind) { continue } - repoName := getName(streamNode.ImageStream) + refCounter++ + if _, processed := processedImages[image]; processed { + markedForDeletionCounter++ + } - glog.V(4).Infof("Pruning manifest %s in the repository %s/%s", imageNode.Image.Name, registryURL.Host, repoName) - if err := manifestPruner.DeleteManifest(registryClient, registryURL, repoName, imageNode.Image.Name); err != nil { - errs = append(errs, fmt.Errorf("error pruning manifest %s in the repository %s/%s: %v", - imageNode.Image.Name, registryURL.Host, repoName, err)) + if refCounter-markedForDeletionCounter > 1 { + components.AddReferencingStreams(comp, false, stream) + continue streamLoop } } + + switch { + // there's just one remaining strong reference from the stream -> unlink + case refCounter < 2: + components.AddReferencingStreams(comp, true, stream) + // there's just one remaining strong reference and at least one another reference now being + // dereferenced in a running job -> wait until it completes + case refCounter-markedForDeletionCounter < 2: + return true + // not yet prunable + default: + components.AddReferencingStreams(comp, false, stream) + } } - return errs + return false +} + +// imageComponentIsPrunable returns true if the image component is not referenced by any images. +func imageComponentIsPrunable(g genericgraph.Graph, cn *imagegraph.ImageComponentNode) bool { + for _, predecessor := range g.To(cn) { + glog.V(4).Infof("Examining predecessor %#v of image config %v", predecessor, cn) + if g.Kind(predecessor) == imagegraph.ImageNodeKind { + glog.V(4).Infof("Config %v has an image predecessor", cn) + return false + } + } + + return true +} + +// streamReferencingImageComponent returns a list of ImageStreamNodes that reference a +// given ImageComponentNode. +func streamsReferencingImageComponent(g genericgraph.Graph, cn *imagegraph.ImageComponentNode) []*imagegraph.ImageStreamNode { + ret := []*imagegraph.ImageStreamNode{} + for _, predecessor := range g.To(cn) { + if g.Kind(predecessor) != imagegraph.ImageStreamNodeKind { + continue + } + ret = append(ret, predecessor.(*imagegraph.ImageStreamNode)) + } + + return ret } // imageDeleter removes an image from OpenShift. @@ -1316,39 +1703,6 @@ func (p *manifestDeleter) DeleteManifest(registryClient *http.Client, registryUR return deleteFromRegistry(registryClient, fmt.Sprintf("%s/v2/%s/manifests/%s", registryURL.String(), repoName, manifest)) } -func getName(obj runtime.Object) string { - accessor, err := kmeta.Accessor(obj) - if err != nil { - glog.V(4).Infof("Error getting accessor for %#v", obj) - return "" - } - ns := accessor.GetNamespace() - if len(ns) == 0 { - return accessor.GetName() - } - return fmt.Sprintf("%s/%s", ns, accessor.GetName()) -} - -func getKindName(obj *kapi.ObjectReference) string { - if obj == nil { - return "unknown object" - } - name := obj.Name - if len(obj.Namespace) > 0 { - name = obj.Namespace + "/" + name - } - return fmt.Sprintf("%s[%s]", obj.Kind, name) -} - -func getRef(obj runtime.Object) *kapi.ObjectReference { - ref, err := kapiref.GetReference(legacyscheme.Scheme, obj) - if err != nil { - glog.Errorf("failed to get reference to object %T: %v", obj, err) - return nil - } - return ref -} - func makeISTag(namespace, name, tag string) *imageapi.ImageStreamTag { return &imageapi.ImageStreamTag{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/oc/admin/prune/imageprune/prune_test.go b/pkg/oc/admin/prune/imageprune/prune_test.go index f02232fa7f7f..144663a83080 100644 --- a/pkg/oc/admin/prune/imageprune/prune_test.go +++ b/pkg/oc/admin/prune/imageprune/prune_test.go @@ -2,19 +2,26 @@ package imageprune import ( "bytes" + "errors" "flag" "fmt" "io/ioutil" "net/http" "net/url" "reflect" + "regexp" + "sort" + "sync" "testing" "time" + "github.com/golang/glog" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/rest/fake" clientgotesting "k8s.io/client-go/testing" kapi "k8s.io/kubernetes/pkg/apis/core" @@ -23,7 +30,8 @@ import ( appsapi "github.com/openshift/origin/pkg/apps/apis/apps" buildapi "github.com/openshift/origin/pkg/build/apis/build" imageapi "github.com/openshift/origin/pkg/image/apis/image" - imageclient "github.com/openshift/origin/pkg/image/generated/internalclientset/fake" + fakeimageclient "github.com/openshift/origin/pkg/image/generated/internalclientset/fake" + imageclient "github.com/openshift/origin/pkg/image/generated/internalclientset/typed/image/internalversion" "github.com/openshift/origin/pkg/oc/admin/prune/imageprune/testutil" "github.com/openshift/origin/pkg/oc/graph/genericgraph" imagegraph "github.com/openshift/origin/pkg/oc/graph/imagegraph/nodes" @@ -44,29 +52,36 @@ func TestImagePruning(t *testing.T) { registryURL := "https://" + registryHost tests := []struct { - name string - pruneOverSizeLimit *bool - allImages *bool - pruneRegistry *bool - ignoreInvalidRefs *bool - keepTagRevisions *int - namespace string - images imageapi.ImageList - pods kapi.PodList - streams imageapi.ImageStreamList - rcs kapi.ReplicationControllerList - bcs buildapi.BuildConfigList - builds buildapi.BuildList - dss kapisext.DaemonSetList - deployments kapisext.DeploymentList - dcs appsapi.DeploymentConfigList - rss kapisext.ReplicaSetList - limits map[string][]*kapi.LimitRange - expectedImageDeletions []string - expectedStreamUpdates []string - expectedLayerLinkDeletions []string - expectedBlobDeletions []string - expectedErrorString string + name string + pruneOverSizeLimit *bool + allImages *bool + pruneRegistry *bool + ignoreInvalidRefs *bool + keepTagRevisions *int + namespace string + images imageapi.ImageList + pods kapi.PodList + streams imageapi.ImageStreamList + rcs kapi.ReplicationControllerList + bcs buildapi.BuildConfigList + builds buildapi.BuildList + dss kapisext.DaemonSetList + deployments kapisext.DeploymentList + dcs appsapi.DeploymentConfigList + rss kapisext.ReplicaSetList + limits map[string][]*kapi.LimitRange + imageDeleterErr error + imageStreamDeleterErr error + layerDeleterErr error + manifestDeleterErr error + blobDeleterErrorGetter errorForSHA + expectedImageDeletions []string + expectedStreamUpdates []string + expectedLayerLinkDeletions []string + expectedManifestLinkDeletions []string + expectedBlobDeletions []string + expectedFailures []string + expectedErrorString string }{ { name: "1 pod - phase pending - don't prune", @@ -110,6 +125,7 @@ func TestImagePruning(t *testing.T) { pods: testutil.PodList(testutil.Pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL + "|" + testutil.Layer1, registryURL + "|" + testutil.Layer2, registryURL + "|" + testutil.Layer3, @@ -151,6 +167,7 @@ func TestImagePruning(t *testing.T) { ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL + "|" + testutil.Layer1, registryURL + "|" + testutil.Layer2, registryURL + "|" + testutil.Layer3, @@ -169,6 +186,7 @@ func TestImagePruning(t *testing.T) { ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL + "|" + testutil.Layer1, registryURL + "|" + testutil.Layer2, registryURL + "|" + testutil.Layer3, @@ -185,6 +203,7 @@ func TestImagePruning(t *testing.T) { ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL + "|" + testutil.Layer1, registryURL + "|" + testutil.Layer2, registryURL + "|" + testutil.Layer3, @@ -201,6 +220,7 @@ func TestImagePruning(t *testing.T) { ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL + "|" + testutil.Layer1, registryURL + "|" + testutil.Layer2, registryURL + "|" + testutil.Layer3, @@ -217,6 +237,7 @@ func TestImagePruning(t *testing.T) { ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL + "|" + testutil.Layer1, registryURL + "|" + testutil.Layer2, registryURL + "|" + testutil.Layer3, @@ -247,6 +268,7 @@ func TestImagePruning(t *testing.T) { ), dss: testutil.DSList(testutil.DS("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000001"}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, { @@ -257,6 +279,7 @@ func TestImagePruning(t *testing.T) { ), rss: testutil.RSList(testutil.RS("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000001"}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, { @@ -267,6 +290,7 @@ func TestImagePruning(t *testing.T) { ), deployments: testutil.DeploymentList(testutil.Deployment("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000001"}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, { @@ -371,8 +395,121 @@ func TestImagePruning(t *testing.T) { ), )), ), - expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000004"}, - expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedManifestLinkDeletions: []string{registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedBlobDeletions: []string{registryURL + "|" + "sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + }, + + { + name: "continue on blob deletion failure", + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", nil, "layer1", "layer2"), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + ), + )), + ), + blobDeleterErrorGetter: func(dgst string) error { + if dgst == "layer1" { + return errors.New("err") + } + return nil + }, + expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedManifestLinkDeletions: []string{registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedLayerLinkDeletions: []string{registryURL + "|foo/bar|layer1", registryURL + "|foo/bar|layer2"}, + expectedBlobDeletions: []string{ + registryURL + "|" + "layer1", + registryURL + "|" + "layer2", + registryURL + "|" + "sha256:0000000000000000000000000000000000000000000000000000000000000004", + }, + expectedFailures: []string{registryURL + "|" + "layer1|err"}, + }, + + { + name: "keep image when all blob deletions fail", + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", nil, "layer1", "layer2"), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + ), + )), + ), + blobDeleterErrorGetter: func(dgst string) error { return errors.New("err") }, + expectedImageDeletions: []string{}, + expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedManifestLinkDeletions: []string{registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedLayerLinkDeletions: []string{registryURL + "|foo/bar|layer1", registryURL + "|foo/bar|layer2"}, + expectedBlobDeletions: []string{registryURL + "|layer1", registryURL + "|layer2", registryURL + "|" + "sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedFailures: []string{registryURL + "|" + "layer1|err", registryURL + "|" + "layer2|err", registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000004|err"}, + }, + + { + name: "continue on manifest link deletion failure", + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + ), + )), + ), + manifestDeleterErr: fmt.Errorf("err"), + expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedManifestLinkDeletions: []string{registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedBlobDeletions: []string{registryURL + "|" + "sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedFailures: []string{registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004|err"}, + }, + + { + name: "stop on image stream update failure", + images: testutil.ImageList( + testutil.UnmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + ), + )), + ), + imageStreamDeleterErr: fmt.Errorf("err"), + expectedFailures: []string{"foo/bar|err"}, }, { @@ -453,7 +590,7 @@ func TestImagePruning(t *testing.T) { "foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000000", "foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000002", }, - expectedBlobDeletions: []string{registryURL + "|layer1"}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000001", registryURL + "|layer1"}, }, { @@ -471,6 +608,7 @@ func TestImagePruning(t *testing.T) { )), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000002"}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000002"}, }, { @@ -526,6 +664,18 @@ func TestImagePruning(t *testing.T) { "foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004", "foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000005", }, + expectedManifestLinkDeletions: []string{ + registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000001", + registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000003", + registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004", + registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000005", + }, + expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000001", + registryURL + "|" + "sha256:0000000000000000000000000000000000000000000000000000000000000003", + registryURL + "|" + "sha256:0000000000000000000000000000000000000000000000000000000000000004", + registryURL + "|" + "sha256:0000000000000000000000000000000000000000000000000000000000000005", + }, }, { @@ -558,6 +708,7 @@ func TestImagePruning(t *testing.T) { ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedStreamUpdates: []string{}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000"}, }, { @@ -568,6 +719,7 @@ func TestImagePruning(t *testing.T) { ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedStreamUpdates: []string{}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000"}, }, { @@ -587,6 +739,7 @@ func TestImagePruning(t *testing.T) { ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedStreamUpdates: []string{}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000"}, }, { @@ -608,6 +761,14 @@ func TestImagePruning(t *testing.T) { "sha256:0000000000000000000000000000000000000000000000000000000000000005", }, expectedStreamUpdates: []string{}, + expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000", + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000001", + registryURL + "|" + "sha256:0000000000000000000000000000000000000000000000000000000000000002", + registryURL + "|" + "sha256:0000000000000000000000000000000000000000000000000000000000000003", + registryURL + "|" + "sha256:0000000000000000000000000000000000000000000000000000000000000004", + registryURL + "|" + "sha256:0000000000000000000000000000000000000000000000000000000000000005", + }, }, { @@ -618,6 +779,7 @@ func TestImagePruning(t *testing.T) { ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedStreamUpdates: []string{}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000"}, }, { @@ -640,6 +802,14 @@ func TestImagePruning(t *testing.T) { "sha256:0000000000000000000000000000000000000000000000000000000000000005", }, expectedStreamUpdates: []string{}, + expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000000", + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000001", + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000002", + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000003", + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000004", + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000005", + }, }, { @@ -693,7 +863,9 @@ func TestImagePruning(t *testing.T) { registryURL + "|foo/bar|layer7", registryURL + "|foo/bar|layer8", }, + expectedManifestLinkDeletions: []string{registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000004", registryURL + "|layer5", registryURL + "|layer6", registryURL + "|layer7", @@ -701,6 +873,49 @@ func TestImagePruning(t *testing.T) { }, }, + { + name: "continue on layer link error", + images: testutil.ImageList( + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &testutil.Config1, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &testutil.Config2, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", nil, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", nil, "layer5", "layer6", "layer7", "layer8"), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + ), + )), + ), + layerDeleterErr: fmt.Errorf("err"), + expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedManifestLinkDeletions: []string{registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000004", + registryURL + "|layer5", + registryURL + "|layer6", + registryURL + "|layer7", + registryURL + "|layer8", + }, + expectedLayerLinkDeletions: []string{ + registryURL + "|foo/bar|layer5", + registryURL + "|foo/bar|layer6", + registryURL + "|foo/bar|layer7", + registryURL + "|foo/bar|layer8", + }, + expectedFailures: []string{ + registryURL + "|foo/bar|layer5|err", + registryURL + "|foo/bar|layer6|err", + registryURL + "|foo/bar|layer7|err", + registryURL + "|foo/bar|layer8|err", + }, + }, + { name: "images with duplicate layers and configs", images: testutil.ImageList( @@ -729,7 +944,10 @@ func TestImagePruning(t *testing.T) { registryURL + "|foo/bar|layer7", registryURL + "|foo/bar|layer8", }, + expectedManifestLinkDeletions: []string{registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000004", + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000005", registryURL + "|" + testutil.Config2, registryURL + "|layer5", registryURL + "|layer6", @@ -740,6 +958,47 @@ func TestImagePruning(t *testing.T) { }, }, + { + name: "continue on image deletion failure", + images: testutil.ImageList( + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &testutil.Config1, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &testutil.Config1, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", &testutil.Config1, "layer1", "layer2", "layer3", "layer4"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", &testutil.Config2, "layer5", "layer6", "layer7", "layer8"), + testutil.ImageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000005", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000005", &testutil.Config2, "layer5", "layer6", "layer9", "layerX"), + ), + streams: testutil.StreamList( + testutil.Stream(registryHost, "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + ), + )), + ), + imageDeleterErr: fmt.Errorf("err"), + expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000004", "sha256:0000000000000000000000000000000000000000000000000000000000000005"}, + expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedLayerLinkDeletions: []string{ + registryURL + "|foo/bar|" + testutil.Config2, + registryURL + "|foo/bar|layer5", + registryURL + "|foo/bar|layer6", + registryURL + "|foo/bar|layer7", + registryURL + "|foo/bar|layer8", + }, + expectedManifestLinkDeletions: []string{registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000004", + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000005", + registryURL + "|layer7", + registryURL + "|layer8", + registryURL + "|layer9", + registryURL + "|layerX", + }, + expectedFailures: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000004|err", "sha256:0000000000000000000000000000000000000000000000000000000000000005|err"}, + }, + { name: "layers shared with young images are not pruned", images: testutil.ImageList( @@ -747,6 +1006,7 @@ func TestImagePruning(t *testing.T) { testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 5), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000001"}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000001"}, }, { @@ -769,8 +1029,10 @@ func TestImagePruning(t *testing.T) { limits: map[string][]*kapi.LimitRange{ "foo": testutil.LimitList(100, 200), }, - expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000003"}, - expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000003"}, + expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000003"}, + expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000003"}, + expectedManifestLinkDeletions: []string{registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000003"}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000003"}, }, { @@ -802,6 +1064,14 @@ func TestImagePruning(t *testing.T) { }, expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000002", "sha256:0000000000000000000000000000000000000000000000000000000000000004"}, expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000002", "bar/foo|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, + expectedManifestLinkDeletions: []string{ + registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000002", + registryURL + "|bar/foo|sha256:0000000000000000000000000000000000000000000000000000000000000004", + }, + expectedBlobDeletions: []string{ + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000002", + registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000004", + }, }, { @@ -876,8 +1146,10 @@ func TestImagePruning(t *testing.T) { limits: map[string][]*kapi.LimitRange{ "foo": testutil.LimitList(100, 200), }, - expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000003"}, - expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000003"}, + expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000003"}, + expectedManifestLinkDeletions: []string{registryURL + "|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000003"}, + expectedBlobDeletions: []string{registryURL + "|sha256:0000000000000000000000000000000000000000000000000000000000000003"}, + expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000003"}, }, { @@ -908,20 +1180,23 @@ func TestImagePruning(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { options := PrunerOptions{ - Namespace: test.namespace, - AllImages: test.allImages, - Images: &test.images, - Streams: &test.streams, - Pods: &test.pods, - RCs: &test.rcs, - BCs: &test.bcs, - Builds: &test.builds, - DSs: &test.dss, - Deployments: &test.deployments, - DCs: &test.dcs, - RSs: &test.rss, - LimitRanges: test.limits, - RegistryURL: &url.URL{Scheme: "https", Host: registryHost}, + Namespace: test.namespace, + AllImages: test.allImages, + Images: &test.images, + ImageWatcher: watch.NewFake(), + Streams: &test.streams, + StreamWatcher: watch.NewFake(), + Pods: &test.pods, + RCs: &test.rcs, + BCs: &test.bcs, + Builds: &test.builds, + DSs: &test.dss, + Deployments: &test.deployments, + DCs: &test.dcs, + RSs: &test.rss, + LimitRanges: test.limits, + RegistryClientFactory: FakeRegistryClientFactory, + RegistryURL: &url.URL{Scheme: "https", Host: registryHost}, } if test.pruneOverSizeLimit != nil { options.PruneOverSizeLimit = test.pruneOverSizeLimit @@ -955,14 +1230,33 @@ func TestImagePruning(t *testing.T) { return } - imageDeleter := &fakeImageDeleter{invocations: sets.NewString()} - streamDeleter := &fakeImageStreamDeleter{invocations: sets.NewString()} - layerLinkDeleter := &fakeLayerLinkDeleter{invocations: sets.NewString()} - blobDeleter := &fakeBlobDeleter{invocations: sets.NewString()} - manifestDeleter := &fakeManifestDeleter{invocations: sets.NewString()} - - if err := p.Prune(imageDeleter, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter); err != nil { - t.Fatalf("unexpected error: %v", err) + imageDeleter, imageDeleterFactory := newFakeImageDeleter(test.imageDeleterErr) + streamDeleter := &fakeImageStreamDeleter{err: test.imageStreamDeleterErr, invocations: sets.NewString()} + layerLinkDeleter := &fakeLayerLinkDeleter{err: test.layerDeleterErr, invocations: sets.NewString()} + blobDeleter := &fakeBlobDeleter{getError: test.blobDeleterErrorGetter, invocations: sets.NewString()} + manifestDeleter := &fakeManifestDeleter{err: test.manifestDeleterErr, invocations: sets.NewString()} + + deletions, failures := p.Prune(imageDeleterFactory, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter) + + expectedFailures := sets.NewString(test.expectedFailures...) + renderedFailures := sets.NewString() + for _, f := range failures { + rendered := renderFailure(registryURL, &f) + if renderedFailures.Has(rendered) { + t.Errorf("got the following failure more than once: %v", rendered) + continue + } + renderedFailures.Insert(rendered) + } + for f := range renderedFailures { + if expectedFailures.Has(f) { + expectedFailures.Delete(f) + continue + } + t.Errorf("got unexpected failure: %v", f) + } + for f := range expectedFailures { + t.Errorf("the following expected failure was not returned: %v", f) } expectedImageDeletions := sets.NewString(test.expectedImageDeletions...) @@ -980,14 +1274,81 @@ func TestImagePruning(t *testing.T) { t.Errorf("unexpected layer link deletions: %s", diff.ObjectDiff(a, e)) } + expectedManifestLinkDeletions := sets.NewString(test.expectedManifestLinkDeletions...) + if a, e := manifestDeleter.invocations, expectedManifestLinkDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("unexpected manifest link deletions: %s", diff.ObjectDiff(a, e)) + } + expectedBlobDeletions := sets.NewString(test.expectedBlobDeletions...) if a, e := blobDeleter.invocations, expectedBlobDeletions; !reflect.DeepEqual(a, e) { t.Errorf("unexpected blob deletions: %s", diff.ObjectDiff(a, e)) } + + // TODO: shall we return deletion for each layer link unlinked from the image stream?? + imageStreamUpdates := sets.NewString() + expectedAllDeletions := sets.NewString() + for _, s := range []sets.String{expectedImageDeletions, expectedLayerLinkDeletions, expectedBlobDeletions} { + expectedAllDeletions.Insert(s.List()...) + } + for _, d := range deletions { + rendered, isImageStreamUpdate, isManifestLinkDeletion := renderDeletion(registryURL, &d) + if isManifestLinkDeletion { + continue + } + if isImageStreamUpdate { + imageStreamUpdates.Insert(rendered) + continue + } + if expectedAllDeletions.Has(rendered) { + expectedAllDeletions.Delete(rendered) + } else { + t.Errorf("got unexpected deletion: %#+v (rendered: %q)", d, rendered) + } + } + for _, f := range failures { + rendered, _, _ := renderDeletion(registryURL, &Deletion{Node: f.Node, Parent: f.Parent}) + expectedAllDeletions.Delete(rendered) + } + for del, ok := expectedAllDeletions.PopAny(); ok; del, ok = expectedAllDeletions.PopAny() { + t.Errorf("expected deletion %q did not happen", del) + } + + expectedStreamUpdateNames := sets.NewString() + for u := range expectedStreamUpdates { + expectedStreamUpdateNames.Insert(regexp.MustCompile(`[@|:]`).Split(u, 2)[0]) + } + if a, e := imageStreamUpdates, expectedStreamUpdateNames; !reflect.DeepEqual(a, e) { + t.Errorf("unexpected image stream updates in deletions: %s", diff.ObjectDiff(a, e)) + } }) } } +func renderDeletion(registryURL string, deletion *Deletion) (rendered string, isImageStreamUpdate, isManifestLinkDeletion bool) { + switch t := deletion.Node.(type) { + case *imagegraph.ImageNode: + return t.Image.Name, false, false + case *imagegraph.ImageComponentNode: + // deleting blob + if deletion.Parent == nil { + return fmt.Sprintf("%s|%s", registryURL, t.Component), false, false + } + streamName := "unknown" + if sn, ok := deletion.Parent.(*imagegraph.ImageStreamNode); ok { + streamName = getName(sn.ImageStream) + } + return fmt.Sprintf("%s|%s|%s", registryURL, streamName, t.Component), false, t.Type == imagegraph.ImageComponentTypeManifest + case *imagegraph.ImageStreamNode: + return getName(t.ImageStream), true, false + } + return "unknown", false, false +} + +func renderFailure(registryURL string, failure *Failure) string { + rendered, _, _ := renderDeletion(registryURL, &Deletion{Node: failure.Node, Parent: failure.Parent}) + return rendered + "|" + failure.Err.Error() +} + func TestImageDeleter(t *testing.T) { flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel)) @@ -1001,7 +1362,7 @@ func TestImageDeleter(t *testing.T) { } for name, test := range tests { - imageClient := imageclient.Clientset{} + imageClient := fakeimageclient.Clientset{} imageClient.AddReactor("delete", "images", func(action clientgotesting.Action) (handled bool, ret runtime.Object, err error) { return true, nil, test.imageDeletionError }) @@ -1096,6 +1457,7 @@ func TestRegistryPruning(t *testing.T) { "https://registry1.io|foo/bar|layer2", ), expectedBlobDeletions: sets.NewString( + "https://registry1.io|sha256:0000000000000000000000000000000000000000000000000000000000000001", "https://registry1.io|"+testutil.Config1, "https://registry1.io|layer1", "https://registry1.io|layer2", @@ -1132,6 +1494,8 @@ func TestRegistryPruning(t *testing.T) { ), expectedLayerLinkDeletions: sets.NewString(), expectedBlobDeletions: sets.NewString( + "https://registry1.io|sha256:0000000000000000000000000000000000000000000000000000000000000001", + "https://registry1.io|sha256:0000000000000000000000000000000000000000000000000000000000000002", "https://registry1.io|"+testutil.Config1, "https://registry1.io|"+testutil.Config2, "https://registry1.io|layer1", @@ -1169,9 +1533,9 @@ func TestRegistryPruning(t *testing.T) { expectedLayerLinkDeletions: sets.NewString( "https://registry1.io|foo/bar|layer1", "https://registry1.io|foo/bar|layer2", - // TODO: ideally, pruner should remove layers of id2 from foo/bar as well ), expectedBlobDeletions: sets.NewString( + "https://registry1.io|sha256:0000000000000000000000000000000000000000000000000000000000000001", "https://registry1.io|layer1", "https://registry1.io|layer2", ), @@ -1217,7 +1581,9 @@ func TestRegistryPruning(t *testing.T) { KeepTagRevisions: &keepTagRevisions, PruneRegistry: &test.pruneRegistry, Images: &test.images, + ImageWatcher: watch.NewFake(), Streams: &test.streams, + StreamWatcher: watch.NewFake(), Pods: &kapi.PodList{}, RCs: &kapi.ReplicationControllerList{}, BCs: &buildapi.BuildConfigList{}, @@ -1226,20 +1592,21 @@ func TestRegistryPruning(t *testing.T) { Deployments: &kapisext.DeploymentList{}, DCs: &appsapi.DeploymentConfigList{}, RSs: &kapisext.ReplicaSetList{}, - RegistryURL: &url.URL{Scheme: "https", Host: "registry1.io"}, + RegistryClientFactory: FakeRegistryClientFactory, + RegistryURL: &url.URL{Scheme: "https", Host: "registry1.io"}, } p, err := NewPruner(options) if err != nil { t.Fatalf("unexpected error: %v", err) } - imageDeleter := &fakeImageDeleter{invocations: sets.NewString()} + _, imageDeleterFactory := newFakeImageDeleter(nil) streamDeleter := &fakeImageStreamDeleter{invocations: sets.NewString()} layerLinkDeleter := &fakeLayerLinkDeleter{invocations: sets.NewString()} blobDeleter := &fakeBlobDeleter{invocations: sets.NewString()} manifestDeleter := &fakeManifestDeleter{invocations: sets.NewString()} - p.Prune(imageDeleter, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter) + p.Prune(imageDeleterFactory, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter) if a, e := layerLinkDeleter.invocations, test.expectedLayerLinkDeletions; !reflect.DeepEqual(a, e) { t.Errorf("unexpected layer link deletions: %s", diff.ObjectDiff(a, e)) @@ -1290,16 +1657,18 @@ func TestImageWithStrongAndWeakRefsIsNotPruned(t *testing.T) { rss := testutil.RSList() options := PrunerOptions{ - Images: &images, - Streams: &streams, - Pods: &pods, - RCs: &rcs, - BCs: &bcs, - Builds: &builds, - DSs: &dss, - Deployments: &deployments, - DCs: &dcs, - RSs: &rss, + Images: &images, + ImageWatcher: watch.NewFake(), + Streams: &streams, + StreamWatcher: watch.NewFake(), + Pods: &pods, + RCs: &rcs, + BCs: &bcs, + Builds: &builds, + DSs: &dss, + Deployments: &deployments, + DCs: &dcs, + RSs: &rss, } keepYoungerThan := 24 * time.Hour keepTagRevisions := 2 @@ -1310,14 +1679,19 @@ func TestImageWithStrongAndWeakRefsIsNotPruned(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - imageDeleter := &fakeImageDeleter{invocations: sets.NewString()} + imageDeleter, imageDeleterFactory := newFakeImageDeleter(nil) streamDeleter := &fakeImageStreamDeleter{invocations: sets.NewString()} layerLinkDeleter := &fakeLayerLinkDeleter{invocations: sets.NewString()} blobDeleter := &fakeBlobDeleter{invocations: sets.NewString()} manifestDeleter := &fakeManifestDeleter{invocations: sets.NewString()} - if err := p.Prune(imageDeleter, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter); err != nil { - t.Fatalf("unexpected error: %v", err) + deletions, failures := p.Prune(imageDeleterFactory, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter) + if len(failures) != 0 { + t.Errorf("got unexpected failures: %#+v", failures) + } + + if len(deletions) > 0 { + t.Fatalf("got unexpected deletions: %#+v", deletions) } if imageDeleter.invocations.Len() > 0 { @@ -1349,11 +1723,358 @@ func TestImageIsPrunable(t *testing.T) { } } +func TestPrunerGetNextJob(t *testing.T) { + flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel)) + + glog.V(2).Infof("debug") + algo := pruneAlgorithm{ + keepYoungerThan: time.Now(), + } + p := &pruner{algorithm: algo, processedImages: make(map[*imagegraph.ImageNode]*Job)} + images := testutil.ImageList( + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 1, "layer1"), + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 2, "layer1", "layer2"), + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", 3, "Layer1", "Layer2", "Layer3"), + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000013", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000013", 4, "Layer1", "LayeR2", "LayeR3"), + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000012", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000012", 5, "LayeR1", "LayeR2"), + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000011", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000011", 6, "layer1", "Layer2", "LAYER3", "LAYER4"), + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000010", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000010", 7, "layer1", "layer2", "layer3", "layer4"), + ) + p.g = genericgraph.New() + err := p.addImagesToGraph(&images) + if err != nil { + t.Fatalf("failed to add images: %v", err) + } + + is := images.Items + imageStreams := testutil.StreamList( + testutil.Stream("example.com", "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent(is[3].Name, is[3].DockerImageReference), + testutil.TagEvent(is[4].Name, is[4].DockerImageReference), + testutil.TagEvent(is[5].Name, is[5].DockerImageReference)))), + testutil.Stream("example.com", "foo", "baz", testutil.Tags( + testutil.Tag("devel", + testutil.TagEvent(is[3].Name, is[3].DockerImageReference), + testutil.TagEvent(is[2].Name, is[2].DockerImageReference), + testutil.TagEvent(is[1].Name, is[1].DockerImageReference)), + testutil.Tag("prod", + testutil.TagEvent(is[2].Name, is[2].DockerImageReference))))) + if err := p.addImageStreamsToGraph(&imageStreams, nil); err != nil { + t.Fatalf("failed to add image streams: %v", err) + } + + imageNodes := getImageNodes(p.g.Nodes()) + if len(imageNodes) == 0 { + t.Fatalf("not images nodes") + } + prunable := calculatePrunableImages(p.g, imageNodes, algo) + sort.Sort(byLayerCountAndAge(prunable)) + p.queue = makeQueue(prunable) + + checkQueue := func(desc string, expected ...*imageapi.Image) { + for i, item := 0, p.queue; i < len(expected) || item != nil; i++ { + if i >= len(expected) { + t.Errorf("[%s] unexpected image at #%d: %s", desc, i, item.node.Image.Name) + } else if item == nil { + t.Errorf("[%s] expected image %q not found at #%d", desc, expected[i].Name, i) + } else if item.node.Image.Name != expected[i].Name { + t.Errorf("[%s] unexpected image at #%d: %s != %s", desc, i, item.node.Image.Name, expected[i].Name) + } + if item != nil { + item = item.next + } + } + if t.Failed() { + t.FailNow() + } + } + + /* layerrefs: layer1:4, Layer1:2, LayeR1:1, layer2:2, Layer2:2, LayeR2:2, + * layer3:1, Layer3:1, LayeR3:1, LAYER3:1, layer4:1, LAYER4:1 */ + checkQueue("initial state", &is[6], &is[5], &is[3], &is[2], &is[4], &is[1], &is[0]) + job := expectBlockedOrJob(t, p, "pop first", false, &is[6], []string{"layer4", "layer3"})(p.getNextJob()) + p.processedImages[job.Image] = job + imgnd6 := job.Image + + /* layerrefs: layer1:3, Layer1:2, LayeR1:1, layer2:1, Layer2:2, LayeR2:2, + * layer3:0, Layer3:1, LayeR3:1, LAYER3:1, layer4:0, LAYER4:1 */ + checkQueue("1 removed", &is[5], &is[3], &is[2], &is[4], &is[1], &is[0]) + job = expectBlockedOrJob(t, p, "pop second", false, &is[5], []string{"LAYER3", "LAYER4"})(p.getNextJob()) + p.processedImages[job.Image] = job + imgnd5 := job.Image + + /* layerrefs: layer1:2, Layer1:2, LayeR1:1, layer2:1, Layer2:1, LayeR2:2, + * Layer3:1, LayeR3:1, LAYER3:0, LAYER4:0 */ + checkQueue("2 removed", &is[3], &is[2], &is[4], &is[1], &is[0]) + job = expectBlockedOrJob(t, p, "pop third", false, &is[3], []string{"LayeR3"})(p.getNextJob()) + p.processedImages[job.Image] = job + imgnd3 := job.Image + + // layerrefs: layer1:2, Layer1:1, LayeR1:1, layer2:1, Layer2:1, LayeR2:1, Layer3:1, LayeR3:0 + checkQueue("3 removed", &is[2], &is[4], &is[1], &is[0]) + // all the remaining images are blocked now except for the is[0] + job = expectBlockedOrJob(t, p, "pop fourth", false, &is[0], nil)(p.getNextJob()) + p.processedImages[job.Image] = job + imgnd0 := job.Image + + // layerrefs: layer1:1, Layer1:1, LayeR1:1, layer2:1, Layer2:1, LayeR2:1, Layer3:1 + checkQueue("4 removed and blocked", &is[2], &is[4], &is[1]) + // all the remaining images are blocked now + expectBlockedOrJob(t, p, "blocked", true, nil, nil)(p.getNextJob()) + + // layerrefs: layer1:1, Layer1:2, LayeR1:1, layer2:1, Layer2:1, LayeR2:1, Layer3:1 + checkQueue("3 to go", &is[2], &is[4], &is[1]) + // unblock one of the images + p.g.RemoveNode(imgnd3) + job = expectBlockedOrJob(t, p, "pop fifth", false, &is[4], + []string{"LayeR1", "LayeR2"})(p.getNextJob()) + p.processedImages[job.Image] = job + imgnd4 := job.Image + + // layerrefs: layer1:1, Layer1:2, LayeR1:0, layer2:1, Layer2:1, LayeR2:0, Layer3:1 + checkQueue("2 to go", &is[2], &is[1]) + expectBlockedOrJob(t, p, "blocked with two items#1", true, nil, nil)(p.getNextJob()) + checkQueue("still 2 to go", &is[2], &is[1]) + + p.g.RemoveNode(imgnd0) + delete(p.processedImages, imgnd0) + expectBlockedOrJob(t, p, "blocked with two items#2", true, nil, nil)(p.getNextJob()) + p.g.RemoveNode(imgnd6) + delete(p.processedImages, imgnd6) + expectBlockedOrJob(t, p, "blocked with two items#3", true, nil, nil)(p.getNextJob()) + p.g.RemoveNode(imgnd4) + delete(p.processedImages, imgnd4) + expectBlockedOrJob(t, p, "blocked with two items#4", true, nil, nil)(p.getNextJob()) + p.g.RemoveNode(imgnd5) + delete(p.processedImages, imgnd5) + + job = expectBlockedOrJob(t, p, "pop sixth", false, &is[2], + []string{"Layer1", "Layer2", "Layer3"})(p.getNextJob()) + p.processedImages[job.Image] = job + + // layerrefs: layer1:1, Layer1:0, layer2:1, Layer2:0, Layer3:0 + checkQueue("1 to go", &is[1]) + job = expectBlockedOrJob(t, p, "pop last", false, &is[1], + []string{"layer1", "layer2"})(p.getNextJob()) + p.processedImages[job.Image] = job + + // layerrefs: layer1:0, layer2:0 + checkQueue("queue empty") + expectBlockedOrJob(t, p, "empty", false, nil, nil)(p.getNextJob()) +} + +func expectBlockedOrJob( + t *testing.T, + p *pruner, + desc string, + blocked bool, + image *imageapi.Image, + layers []string, +) func(job *Job, blocked bool) *Job { + return func(job *Job, b bool) *Job { + if b != blocked { + t.Fatalf("[%s] unexpected blocked: %t != %t", desc, b, blocked) + } + + if blocked { + return job + } + + if image == nil && job != nil { + t.Fatalf("[%s] got unexpected job %#+v", desc, job) + } + if image != nil && job == nil { + t.Fatalf("[%s] got nil instead of job", desc) + } + if job == nil { + return nil + } + + if a, e := job.Image.Image.Name, image.Name; a != e { + t.Errorf("[%s] unexpected image in job: %s != %s", desc, a, e) + } + + expLayers := sets.NewString(imagegraph.EnsureImageComponentManifestNode( + p.g, job.Image.Image.Name).(*imagegraph.ImageComponentNode).String()) + for _, l := range layers { + expLayers.Insert(imagegraph.EnsureImageComponentLayerNode( + p.g, l).(*imagegraph.ImageComponentNode).String()) + } + actLayers := sets.NewString() + for c, ret := range job.Components { + if ret.PrunableGlobally { + actLayers.Insert(c.String()) + } + } + if a, e := actLayers, expLayers; !reflect.DeepEqual(a, e) { + t.Errorf("[%s] unexpected image components: %s", desc, diff.ObjectDiff(a.List(), e.List())) + } + + if t.Failed() { + t.FailNow() + } + + return job + } +} + +func TestChangeImageStreamsWhilePruning(t *testing.T) { + flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel)) + + images := testutil.ImageList( + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", 5), + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 4), + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 3), + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 2), + testutil.AgedImage("sha256:0000000000000000000000000000000000000000000000000000000000000005", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 1), + ) + + streams := testutil.StreamList(testutil.Stream("registry1", "foo", "bar", testutil.Tags())) + streamWatcher := watch.NewFake() + pods := testutil.PodList() + rcs := testutil.RCList() + bcs := testutil.BCList() + builds := testutil.BuildList() + dss := testutil.DSList() + deployments := testutil.DeploymentList() + dcs := testutil.DCList() + rss := testutil.RSList() + + options := PrunerOptions{ + Images: &images, + ImageWatcher: watch.NewFake(), + Streams: &streams, + StreamWatcher: streamWatcher, + Pods: &pods, + RCs: &rcs, + BCs: &bcs, + Builds: &builds, + DSs: &dss, + Deployments: &deployments, + DCs: &dcs, + RSs: &rss, + RegistryClientFactory: FakeRegistryClientFactory, + RegistryURL: &url.URL{Scheme: "https", Host: "registry1.io"}, + NumWorkers: 1, + } + keepYoungerThan := 30 * time.Second + keepTagRevisions := 2 + options.KeepYoungerThan = &keepYoungerThan + options.KeepTagRevisions = &keepTagRevisions + p, err := NewPruner(options) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + pruneFinished := make(chan struct{}) + deletions, failures := []Deletion{}, []Failure{} + imageDeleter, imageDeleterFactory := newBlockingImageDeleter(t) + + // run the pruning loop in a go routine + go func() { + deletions, failures = p.Prune( + imageDeleterFactory, + &fakeImageStreamDeleter{invocations: sets.NewString()}, + &fakeLayerLinkDeleter{invocations: sets.NewString()}, + &fakeBlobDeleter{invocations: sets.NewString()}, + &fakeManifestDeleter{invocations: sets.NewString()}, + ) + if len(failures) != 0 { + t.Errorf("got unexpected failures: %#+v", failures) + } + close(pruneFinished) + }() + + expectedImageDeletions := sets.NewString() + expectedBlobDeletions := sets.NewString() + + img := imageDeleter.waitForRequest() + if a, e := img.Name, images.Items[0].Name; a != e { + t.Fatalf("got unexpected image deletion request: %s != %s", a, e) + } + expectedImageDeletions.Insert(images.Items[0].Name) + expectedBlobDeletions.Insert("registry1|" + images.Items[0].Name) + + // let the pruner wait for reply and meanwhile reference an image with a new image stream + stream := testutil.Stream("registry1", "foo", "new", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1/foo/new@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + ))) + streamWatcher.Add(&stream) + imageDeleter.unblock() + + // the pruner shall skip the newly referenced image + img = imageDeleter.waitForRequest() + if a, e := img.Name, images.Items[2].Name; a != e { + t.Fatalf("got unexpected image deletion request: %s != %s", a, e) + } + expectedImageDeletions.Insert(images.Items[2].Name) + expectedBlobDeletions.Insert("registry1|" + images.Items[2].Name) + + // now lets modify the existing image stream to reference some more images + stream = testutil.Stream("registry1", "foo", "bar", testutil.Tags( + testutil.Tag("latest", + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "registry1/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + testutil.TagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", "registry1/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + ))) + streamWatcher.Modify(&stream) + imageDeleter.unblock() + + // the pruner shall skip the newly referenced image + img = imageDeleter.waitForRequest() + if a, e := img.Name, images.Items[4].Name; a != e { + t.Fatalf("got unexpected image deletion request: %s != %s", a, e) + } + expectedImageDeletions.Insert(images.Items[4].Name) + expectedBlobDeletions.Insert("registry1|" + images.Items[4].Name) + imageDeleter.unblock() + + // no more images - wait for the pruner to finish + select { + case <-pruneFinished: + case <-time.After(time.Second): + t.Errorf("tester: timeout while waiting for pruner to finish") + } + + if a, e := imageDeleter.d.invocations, expectedImageDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("unexpected image deletions: %s", diff.ObjectDiff(a, e)) + } + + expectedAllDeletions := sets.NewString( + append(expectedImageDeletions.List(), expectedBlobDeletions.List()...)...) + for _, d := range deletions { + rendered, _, isManifestLinkDeletion := renderDeletion("registry1", &d) + if isManifestLinkDeletion { + // TODO: update tests to count and verify the number of manifest link deletions + continue + } + if expectedAllDeletions.Has(rendered) { + expectedAllDeletions.Delete(rendered) + } else { + t.Errorf("got unexpected deletion: %#+v (rendered: %q)", d, rendered) + } + } + for del, ok := expectedAllDeletions.PopAny(); ok; del, ok = expectedAllDeletions.PopAny() { + t.Errorf("expected deletion %q did not happen", del) + } +} + +func streamListToClient(list *imageapi.ImageStreamList) imageclient.ImageStreamsGetter { + streams := make([]runtime.Object, 0, len(list.Items)) + for i := range list.Items { + streams = append(streams, &list.Items[i]) + } + + return fakeimageclient.NewSimpleClientset(streams...).Image() +} + func keepTagRevisions(n int) *int { return &n } type fakeImageDeleter struct { + mutex sync.Mutex invocations sets.String err error } @@ -1361,11 +2082,68 @@ type fakeImageDeleter struct { var _ ImageDeleter = &fakeImageDeleter{} func (p *fakeImageDeleter) DeleteImage(image *imageapi.Image) error { + p.mutex.Lock() + defer p.mutex.Unlock() p.invocations.Insert(image.Name) return p.err } +func newFakeImageDeleter(err error) (*fakeImageDeleter, ImagePrunerFactoryFunc) { + deleter := &fakeImageDeleter{ + err: err, + invocations: sets.NewString(), + } + return deleter, func() (ImageDeleter, error) { + return deleter, nil + } +} + +type blockingImageDeleter struct { + t *testing.T + d *fakeImageDeleter + requests chan *imageapi.Image + reply chan struct{} +} + +func (bid *blockingImageDeleter) DeleteImage(img *imageapi.Image) error { + bid.requests <- img + select { + case <-bid.reply: + case <-time.After(time.Second): + bid.t.Fatalf("worker: timeout while waiting for image deletion confirmation") + } + return bid.d.DeleteImage(img) +} + +func (bid *blockingImageDeleter) waitForRequest() *imageapi.Image { + select { + case img := <-bid.requests: + return img + case <-time.After(time.Second): + bid.t.Fatalf("tester: timeout while waiting on worker's request") + return nil + } +} + +func (bid *blockingImageDeleter) unblock() { + bid.reply <- struct{}{} +} + +func newBlockingImageDeleter(t *testing.T) (*blockingImageDeleter, ImagePrunerFactoryFunc) { + deleter, _ := newFakeImageDeleter(nil) + blocking := blockingImageDeleter{ + t: t, + d: deleter, + requests: make(chan *imageapi.Image), + reply: make(chan struct{}), + } + return &blocking, func() (ImageDeleter, error) { + return &blocking, nil + } +} + type fakeImageStreamDeleter struct { + mutex sync.Mutex invocations sets.String err error streamImages map[string][]string @@ -1375,6 +2153,8 @@ type fakeImageStreamDeleter struct { var _ ImageStreamDeleter = &fakeImageStreamDeleter{} func (p *fakeImageStreamDeleter) GetImageStream(stream *imageapi.ImageStream) (*imageapi.ImageStream, error) { + p.mutex.Lock() + defer p.mutex.Unlock() if p.streamImages == nil { p.streamImages = make(map[string][]string) } @@ -1424,19 +2204,28 @@ func (p *fakeImageStreamDeleter) NotifyImageStreamPrune(stream *imageapi.ImageSt return } +type errorForSHA func(dgst string) error + type fakeBlobDeleter struct { + mutex sync.Mutex invocations sets.String - err error + getError errorForSHA } var _ BlobDeleter = &fakeBlobDeleter{} func (p *fakeBlobDeleter) DeleteBlob(registryClient *http.Client, registryURL *url.URL, blob string) error { + p.mutex.Lock() + defer p.mutex.Unlock() p.invocations.Insert(fmt.Sprintf("%s|%s", registryURL.String(), blob)) - return p.err + if p.getError == nil { + return nil + } + return p.getError(blob) } type fakeLayerLinkDeleter struct { + mutex sync.Mutex invocations sets.String err error } @@ -1444,11 +2233,14 @@ type fakeLayerLinkDeleter struct { var _ LayerLinkDeleter = &fakeLayerLinkDeleter{} func (p *fakeLayerLinkDeleter) DeleteLayerLink(registryClient *http.Client, registryURL *url.URL, repo, layer string) error { + p.mutex.Lock() + defer p.mutex.Unlock() p.invocations.Insert(fmt.Sprintf("%s|%s|%s", registryURL.String(), repo, layer)) return p.err } type fakeManifestDeleter struct { + mutex sync.Mutex invocations sets.String err error } @@ -1456,6 +2248,8 @@ type fakeManifestDeleter struct { var _ ManifestDeleter = &fakeManifestDeleter{} func (p *fakeManifestDeleter) DeleteManifest(registryClient *http.Client, registryURL *url.URL, repo, manifest string) error { + p.mutex.Lock() + defer p.mutex.Unlock() p.invocations.Insert(fmt.Sprintf("%s|%s|%s", registryURL.String(), repo, manifest)) return p.err } diff --git a/pkg/oc/admin/prune/imageprune/testutil/util.go b/pkg/oc/admin/prune/imageprune/testutil/util.go index 6590cc22c2ca..ae263b477aa4 100644 --- a/pkg/oc/admin/prune/imageprune/testutil/util.go +++ b/pkg/oc/admin/prune/imageprune/testutil/util.go @@ -38,13 +38,16 @@ func ImageList(images ...imageapi.Image) imageapi.ImageList { } // AgedImage creates a test image with specified age. -func AgedImage(id, ref string, ageInMinutes int64) imageapi.Image { - return CreatedImage(id, ref, time.Now().Add(time.Duration(ageInMinutes)*time.Minute*-1)) +func AgedImage(id, ref string, ageInMinutes int64, layers ...string) imageapi.Image { + return CreatedImage(id, ref, time.Now().Add(time.Duration(ageInMinutes)*time.Minute*-1), layers...) } // CreatedImage creates a test image with the CreationTime set to the given timestamp. -func CreatedImage(id, ref string, created time.Time) imageapi.Image { - image := ImageWithLayers(id, ref, nil, Layer1, Layer2, Layer3, Layer4, Layer5) +func CreatedImage(id, ref string, created time.Time, layers ...string) imageapi.Image { + if len(layers) == 0 { + layers = []string{Layer1, Layer2, Layer3, Layer4, Layer5} + } + image := ImageWithLayers(id, ref, nil, layers...) image.CreationTimestamp = metav1.NewTime(created) return image } diff --git a/pkg/oc/admin/prune/imageprune/worker.go b/pkg/oc/admin/prune/imageprune/worker.go new file mode 100644 index 000000000000..5b128674a1d9 --- /dev/null +++ b/pkg/oc/admin/prune/imageprune/worker.go @@ -0,0 +1,359 @@ +package imageprune + +import ( + "fmt" + "net/http" + "net/url" + + "github.com/golang/glog" + gonum "github.com/gonum/graph" + + kerrapi "k8s.io/apimachinery/pkg/api/errors" + + imagegraph "github.com/openshift/origin/pkg/oc/graph/imagegraph/nodes" +) + +// ComponentRetention knows all the places where image component needs to be pruned (e.g. global blob store +// and repositories). +type ComponentRetention struct { + ReferencingStreams map[*imagegraph.ImageStreamNode]bool + PrunableGlobally bool +} + +// ComponentRetentions contains prunable locations for all the components of an image. +type ComponentRetentions map[*imagegraph.ImageComponentNode]*ComponentRetention + +func (cr ComponentRetentions) add(comp *imagegraph.ImageComponentNode) *ComponentRetention { + if _, ok := cr[comp]; ok { + return cr[comp] + } + cr[comp] = &ComponentRetention{ + ReferencingStreams: make(map[*imagegraph.ImageStreamNode]bool), + } + return cr[comp] +} + +// Add adds component marked as (not) prunable in the blob store. +func (cr ComponentRetentions) Add( + comp *imagegraph.ImageComponentNode, + globallyPrunable bool, +) *ComponentRetention { + r := cr.add(comp) + r.PrunableGlobally = globallyPrunable + return r +} + +// AddReferencingStreams adds a repository location as (not) prunable to the given component. +func (cr ComponentRetentions) AddReferencingStreams( + comp *imagegraph.ImageComponentNode, + prunable bool, + streams ...*imagegraph.ImageStreamNode, +) *ComponentRetention { + r := cr.add(comp) + for _, n := range streams { + r.ReferencingStreams[n] = prunable + } + return r +} + +// Job is an image pruning job for the Worker. It contains information about single image and related +// components. +type Job struct { + Image *imagegraph.ImageNode + Components ComponentRetentions +} + +func enumerateImageComponents( + crs ComponentRetentions, + compType *imagegraph.ImageComponentType, + withPreserved bool, + handler func(comp *imagegraph.ImageComponentNode, prunable bool), +) { + for c, retention := range crs { + if !withPreserved && !retention.PrunableGlobally { + continue + } + if compType != nil && c.Type != *compType { + continue + } + + handler(c, retention.PrunableGlobally) + } +} + +func enumerateImageStreamComponents( + crs ComponentRetentions, + compType *imagegraph.ImageComponentType, + withPreserved bool, + handler func(comp *imagegraph.ImageComponentNode, stream *imagegraph.ImageStreamNode, prunable bool), +) { + for c, cr := range crs { + if compType != nil && c.Type != *compType { + continue + } + + for s, prunable := range cr.ReferencingStreams { + if withPreserved || prunable { + handler(c, s, prunable) + } + } + } +} + +// Deletion denotes a single deletion of a resource as a result of processing a job. If Parent is nil, the +// deletion occured in the global blob store. Otherwise the parent identities repository location. +type Deletion struct { + Node gonum.Node + Parent gonum.Node +} + +// Failure denotes a pruning failure of a single object. +type Failure struct { + Node gonum.Node + Parent gonum.Node + Err error +} + +var _ error = &Failure{} + +func (pf *Failure) Error() string { return pf.String() } + +func (pf *Failure) String() string { + if pf.Node == nil { + return fmt.Sprintf("failed to prune blob: %v", pf.Err) + } + + switch t := pf.Node.(type) { + case *imagegraph.ImageStreamNode: + return fmt.Sprintf("failed to update ImageStream %s: %v", getName(t.ImageStream), pf.Err) + case *imagegraph.ImageNode: + return fmt.Sprintf("failed to delete Image %s: %v", t.Image.DockerImageReference, pf.Err) + case *imagegraph.ImageComponentNode: + detail := "" + if isn, ok := pf.Parent.(*imagegraph.ImageStreamNode); ok { + detail = " in repository " + getName(isn.ImageStream) + } + switch t.Type { + case imagegraph.ImageComponentTypeConfig: + return fmt.Sprintf("failed to delete image config link %s%s: %v", t.Component, detail, pf.Err) + case imagegraph.ImageComponentTypeLayer: + return fmt.Sprintf("failed to delete image layer link %s%s: %v", t.Component, detail, pf.Err) + case imagegraph.ImageComponentTypeManifest: + return fmt.Sprintf("failed to delete image manifest link %s%s: %v", t.Component, detail, pf.Err) + default: + return fmt.Sprintf("failed to delete %s%s: %v", t.String(), detail, pf.Err) + } + default: + return fmt.Sprintf("failed to delete %v: %v", t, pf.Err) + } +} + +// JobResult is a result of job's processing. +type JobResult struct { + Job *Job + Deletions []Deletion + Failures []Failure +} + +func (jr *JobResult) update(deletions []Deletion, failures []Failure) *JobResult { + jr.Deletions = append(jr.Deletions, deletions...) + jr.Failures = append(jr.Failures, failures...) + return jr +} + +// Worker knows how to prune image and its related components. +type Worker interface { + // Run is supposed to be run as a go-rutine. It terminates when nil is received through the in channel. + Run(in <-chan *Job, out chan<- JobResult) +} + +type worker struct { + algorithm pruneAlgorithm + registryClient *http.Client + registryURL *url.URL + imagePruner ImageDeleter + streamPruner ImageStreamDeleter + layerLinkPruner LayerLinkDeleter + blobPruner BlobDeleter + manifestPruner ManifestDeleter +} + +var _ Worker = &worker{} + +// NewWorker creates a new pruning worker. +func NewWorker( + algorithm pruneAlgorithm, + registryClientFactory RegistryClientFactoryFunc, + registryURL *url.URL, + imagePrunerFactory ImagePrunerFactoryFunc, + streamPruner ImageStreamDeleter, + layerLinkPruner LayerLinkDeleter, + blobPruner BlobDeleter, + manifestPruner ManifestDeleter, +) (Worker, error) { + client, err := registryClientFactory() + if err != nil { + return nil, err + } + + imagePruner, err := imagePrunerFactory() + if err != nil { + return nil, err + } + + return &worker{ + algorithm: algorithm, + registryClient: client, + registryURL: registryURL, + imagePruner: imagePruner, + streamPruner: streamPruner, + layerLinkPruner: layerLinkPruner, + blobPruner: blobPruner, + manifestPruner: manifestPruner, + }, nil +} + +func (w *worker) Run(in <-chan *Job, out chan<- JobResult) { + for { + job, more := <-in + if !more { + return + } + out <- *w.prune(job) + } +} + +func (w *worker) prune(job *Job) *JobResult { + res := &JobResult{Job: job} + + blobDeletions, blobFailures := []Deletion{}, []Failure{} + + if w.algorithm.pruneRegistry { + // NOTE: not found errors are treated as success + res.update(pruneImageComponents( + w.registryClient, + w.registryURL, + job.Components, + w.layerLinkPruner, + )) + + blobDeletions, blobFailures = pruneBlobs( + w.registryClient, + w.registryURL, + job.Components, + w.blobPruner, + ) + res.update(blobDeletions, blobFailures) + + res.update(pruneManifests( + w.registryClient, + w.registryURL, + job.Components, + w.manifestPruner, + )) + } + + // Keep the image object when its blobs could not be deleted and the image is ostensibly (we cannot be + // sure unless we ask the registry for blob's existence) still complete. Thanks to the preservation, the + // blobs can be identified and deleted next time. + if len(blobDeletions) > 0 || len(blobFailures) == 0 { + res.update(pruneImages(job.Image, w.imagePruner)) + } + + return res +} + +// pruneImages invokes imagePruner.DeleteImage with each image that is prunable. +func pruneImages( + imageNode *imagegraph.ImageNode, + imagePruner ImageDeleter, +) (deletions []Deletion, failures []Failure) { + err := imagePruner.DeleteImage(imageNode.Image) + if err != nil { + if kerrapi.IsNotFound(err) { + glog.V(2).Infof("Skipping image %s that no longer exists", imageNode.Image.Name) + } else { + failures = append(failures, Failure{Node: imageNode, Err: err}) + } + } else { + deletions = append(deletions, Deletion{Node: imageNode}) + } + + return +} + +// pruneImageComponents invokes layerLinkDeleter.DeleteLayerLink for each repository layer link to +// be deleted from the registry. +func pruneImageComponents( + registryClient *http.Client, + registryURL *url.URL, + crs ComponentRetentions, + layerLinkDeleter LayerLinkDeleter, +) (deletions []Deletion, failures []Failure) { + enumerateImageStreamComponents(crs, nil, false, func( + comp *imagegraph.ImageComponentNode, + stream *imagegraph.ImageStreamNode, + _ bool, + ) { + if comp.Type == imagegraph.ImageComponentTypeManifest { + return + } + streamName := getName(stream.ImageStream) + glog.V(4).Infof("Pruning repository %s/%s: %s", registryURL.Host, streamName, comp.Describe()) + err := layerLinkDeleter.DeleteLayerLink(registryClient, registryURL, streamName, comp.Component) + if err != nil { + failures = append(failures, Failure{Node: comp, Parent: stream, Err: err}) + } else { + deletions = append(deletions, Deletion{Node: comp, Parent: stream}) + } + }) + + return +} + +// pruneBlobs invokes blobPruner.DeleteBlob for each blob to be deleted from the registry. +func pruneBlobs( + registryClient *http.Client, + registryURL *url.URL, + crs ComponentRetentions, + blobPruner BlobDeleter, +) (deletions []Deletion, failures []Failure) { + enumerateImageComponents(crs, nil, false, func(comp *imagegraph.ImageComponentNode, prunable bool) { + err := blobPruner.DeleteBlob(registryClient, registryURL, comp.Component) + if err != nil { + failures = append(failures, Failure{Node: comp, Err: err}) + } else { + deletions = append(deletions, Deletion{Node: comp}) + } + }) + + return +} + +// pruneManifests invokes manifestPruner.DeleteManifest for each repository +// manifest to be deleted from the registry. +func pruneManifests( + registryClient *http.Client, + registryURL *url.URL, + crs ComponentRetentions, + manifestPruner ManifestDeleter, +) (deletions []Deletion, failures []Failure) { + manifestType := imagegraph.ImageComponentTypeManifest + enumerateImageStreamComponents(crs, &manifestType, false, func( + manifestNode *imagegraph.ImageComponentNode, + stream *imagegraph.ImageStreamNode, + _ bool, + ) { + repoName := getName(stream.ImageStream) + + glog.V(4).Infof("Pruning manifest %s in the repository %s/%s", manifestNode.Component, registryURL.Host, repoName) + err := manifestPruner.DeleteManifest(registryClient, registryURL, repoName, manifestNode.Component) + if err != nil { + failures = append(failures, Failure{Node: manifestNode, Parent: stream, Err: err}) + } else { + deletions = append(deletions, Deletion{Node: manifestNode, Parent: stream}) + } + }) + + return +} diff --git a/pkg/oc/admin/prune/images.go b/pkg/oc/admin/prune/images.go index a983e4b5a2e0..bdd3a7a16a7e 100644 --- a/pkg/oc/admin/prune/images.go +++ b/pkg/oc/admin/prune/images.go @@ -15,13 +15,17 @@ import ( "text/tabwriter" "time" + "github.com/golang/glog" + gonum "github.com/gonum/graph" "github.com/spf13/cobra" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kutilerrors "k8s.io/apimachinery/pkg/util/errors" knet "k8s.io/apimachinery/pkg/util/net" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" apimachineryversion "k8s.io/apimachinery/pkg/version" + "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" restclient "k8s.io/client-go/rest" kclientcmd "k8s.io/client-go/tools/clientcmd" @@ -36,6 +40,7 @@ import ( imageapi "github.com/openshift/origin/pkg/image/apis/image" imageclient "github.com/openshift/origin/pkg/image/generated/internalclientset/typed/image/internalversion" "github.com/openshift/origin/pkg/oc/admin/prune/imageprune" + imagegraph "github.com/openshift/origin/pkg/oc/graph/imagegraph/nodes" oserrors "github.com/openshift/origin/pkg/util/errors" "github.com/openshift/origin/pkg/util/netutils" "github.com/openshift/origin/pkg/version" @@ -121,15 +126,16 @@ type PruneImagesOptions struct { PruneRegistry *bool IgnoreInvalidRefs bool - ClientConfig *restclient.Config - AppsClient appsclient.AppsInterface - BuildClient buildclient.BuildInterface - ImageClient imageclient.ImageInterface - DiscoveryClient discovery.DiscoveryInterface - KubeClient kclientset.Interface - Timeout time.Duration - Out io.Writer - ErrOut io.Writer + ClientConfig *restclient.Config + AppsClient appsclient.AppsInterface + BuildClient buildclient.BuildInterface + ImageClient imageclient.ImageInterface + ImageClientFactory func() (imageclient.ImageInterface, error) + DiscoveryClient discovery.DiscoveryInterface + KubeClient kclientset.Interface + Timeout time.Duration + Out io.Writer + ErrOut io.Writer } // NewCmdPruneImages implements the OpenShift cli prune images command. @@ -213,6 +219,7 @@ func (o *PruneImagesOptions) Complete(f kcmdutil.Factory, cmd *cobra.Command, ar o.AppsClient = appsClient o.BuildClient = buildClient o.ImageClient = imageClient + o.ImageClientFactory = getImageClientFactory(f) o.KubeClient = kubeClient o.DiscoveryClient = kubeClient.Discovery() @@ -249,16 +256,6 @@ func (o PruneImagesOptions) Validate() error { // Run contains all the necessary functionality for the OpenShift cli prune images command. func (o PruneImagesOptions) Run() error { - allImages, err := o.ImageClient.Images().List(metav1.ListOptions{}) - if err != nil { - return err - } - - allStreams, err := o.ImageClient.ImageStreams(o.Namespace).List(metav1.ListOptions{}) - if err != nil { - return err - } - allPods, err := o.KubeClient.Core().Pods(o.Namespace).List(metav1.ListOptions{}) if err != nil { return err @@ -330,10 +327,35 @@ func (o PruneImagesOptions) Run() error { limitRangesMap[limit.Namespace] = limits } + allImages, err := o.ImageClient.Images().List(metav1.ListOptions{}) + if err != nil { + return err + } + imageWatcher, err := o.ImageClient.Images().Watch(metav1.ListOptions{}) + if err != nil { + utilruntime.HandleError(fmt.Errorf("internal error: failed to watch for images: %v"+ + "\n - image changes will not be detected", err)) + imageWatcher = watch.NewFake() + } + + imageStreamWatcher, err := o.ImageClient.ImageStreams(o.Namespace).Watch(metav1.ListOptions{}) + if err != nil { + utilruntime.HandleError(fmt.Errorf("internal error: failed to watch for image streams: %v"+ + "\n - image stream changes will not be detected", err)) + imageStreamWatcher = watch.NewFake() + } + defer imageStreamWatcher.Stop() + + allStreams, err := o.ImageClient.ImageStreams(o.Namespace).List(metav1.ListOptions{}) + if err != nil { + return err + } + var ( - registryHost = o.RegistryUrlOverride - registryClient *http.Client - registryPinger imageprune.RegistryPinger + registryHost = o.RegistryUrlOverride + registryClientFactory imageprune.RegistryClientFactoryFunc + registryClient *http.Client + registryPinger imageprune.RegistryPinger ) if o.Confirm { @@ -350,18 +372,24 @@ func (o PruneImagesOptions) Run() error { strings.HasPrefix(registryHost, "http://") } - registryClient, err = getRegistryClient(o.ClientConfig, o.CABundle, insecure) + registryClientFactory = func() (*http.Client, error) { + return getRegistryClient(o.ClientConfig, o.CABundle, insecure) + } + registryClient, err = registryClientFactory() if err != nil { return err } + registryPinger = &imageprune.DefaultRegistryPinger{ Client: registryClient, Insecure: insecure, } } else { registryPinger = &imageprune.DryRunRegistryPinger{} + registryClientFactory = imageprune.FakeRegistryClientFactory } + // verify the registy connection now to avoid future surprises registryURL, err := registryPinger.Ping(registryHost) if err != nil { if len(o.RegistryUrlOverride) == 0 && regexp.MustCompile(registryURLNotReachable).MatchString(err.Error()) { @@ -371,26 +399,28 @@ func (o PruneImagesOptions) Run() error { } options := imageprune.PrunerOptions{ - KeepYoungerThan: o.KeepYoungerThan, - KeepTagRevisions: o.KeepTagRevisions, - PruneOverSizeLimit: o.PruneOverSizeLimit, - AllImages: o.AllImages, - Images: allImages, - Streams: allStreams, - Pods: allPods, - RCs: allRCs, - BCs: allBCs, - Builds: allBuilds, - DSs: allDSs, - Deployments: allDeployments, - DCs: allDCs, - RSs: allRSs, - LimitRanges: limitRangesMap, - DryRun: o.Confirm == false, - RegistryClient: registryClient, - RegistryURL: registryURL, - PruneRegistry: o.PruneRegistry, - IgnoreInvalidRefs: o.IgnoreInvalidRefs, + KeepYoungerThan: o.KeepYoungerThan, + KeepTagRevisions: o.KeepTagRevisions, + PruneOverSizeLimit: o.PruneOverSizeLimit, + AllImages: o.AllImages, + Images: allImages, + ImageWatcher: imageWatcher, + Streams: allStreams, + StreamWatcher: imageStreamWatcher, + Pods: allPods, + RCs: allRCs, + BCs: allBCs, + Builds: allBuilds, + DSs: allDSs, + Deployments: allDeployments, + DCs: allDCs, + RSs: allRSs, + LimitRanges: limitRangesMap, + DryRun: o.Confirm == false, + RegistryClientFactory: registryClientFactory, + RegistryURL: registryURL, + PruneRegistry: o.PruneRegistry, + IgnoreInvalidRefs: o.IgnoreInvalidRefs, } if o.Namespace != metav1.NamespaceAll { options.Namespace = o.Namespace @@ -401,21 +431,27 @@ func (o PruneImagesOptions) Run() error { return fmt.Errorf("failed to build graph - no changes made") } - w := tabwriter.NewWriter(o.Out, 10, 4, 3, ' ', 0) - defer w.Flush() - - imageDeleter := &describingImageDeleter{w: w, errOut: o.ErrOut} - imageStreamDeleter := &describingImageStreamDeleter{w: w, errOut: o.ErrOut} - layerLinkDeleter := &describingLayerLinkDeleter{w: w, errOut: o.ErrOut} - blobDeleter := &describingBlobDeleter{w: w, errOut: o.ErrOut} - manifestDeleter := &describingManifestDeleter{w: w, errOut: o.ErrOut} + imagePrunerFactory := func() (imageprune.ImageDeleter, error) { + return &describingImageDeleter{w: o.Out, errOut: o.ErrOut}, nil + } + imageStreamDeleter := &describingImageStreamDeleter{w: o.Out, errOut: o.ErrOut} + layerLinkDeleter := &describingLayerLinkDeleter{w: o.Out, errOut: o.ErrOut} + blobDeleter := &describingBlobDeleter{w: o.Out, errOut: o.ErrOut} + manifestDeleter := &describingManifestDeleter{w: o.Out, errOut: o.ErrOut} if o.Confirm { - imageDeleter.delegate = imageprune.NewImageDeleter(o.ImageClient) imageStreamDeleter.delegate = imageprune.NewImageStreamDeleter(o.ImageClient) layerLinkDeleter.delegate = imageprune.NewLayerLinkDeleter() blobDeleter.delegate = imageprune.NewBlobDeleter() manifestDeleter.delegate = imageprune.NewManifestDeleter() + + imagePrunerFactory = func() (imageprune.ImageDeleter, error) { + imageClient, err := o.ImageClientFactory() + if err != nil { + return nil, err + } + return imageprune.NewImageDeleter(imageClient), nil + } } else { fmt.Fprintln(o.ErrOut, "Dry run enabled - no modifications will be made. Add --confirm to remove images") } @@ -424,7 +460,99 @@ func (o PruneImagesOptions) Run() error { fmt.Fprintln(o.Out, "Only API objects will be removed. No modifications to the image registry will be made.") } - return pruner.Prune(imageDeleter, imageStreamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter) + deletions, failures := pruner.Prune( + imagePrunerFactory, + imageStreamDeleter, + layerLinkDeleter, + blobDeleter, + manifestDeleter, + ) + printSummary(o.Out, deletions, failures) + if len(failures) == 1 { + return &failures[0] + } + if len(failures) > 0 { + return fmt.Errorf("failed") + } + return nil +} + +func printSummary(out io.Writer, deletions []imageprune.Deletion, failures []imageprune.Failure) { + // TODO: for higher verbosity, sum by error type + if len(failures) == 0 { + fmt.Fprintf(out, "Deleted %d objects.\n", len(deletions)) + } else { + fmt.Fprintf(out, "Deleted %d objects out of %d.\n", len(deletions), len(deletions)+len(failures)) + fmt.Fprintf(out, "Failed to delete %d objects.\n", len(failures)) + } + if !glog.V(2) { + return + } + + fmt.Fprintf(out, "\n") + + w := tabwriter.NewWriter(out, 10, 4, 3, ' ', 0) + defer w.Flush() + + buckets := make(map[string]struct{ deletions, failures, total uint64 }) + count := func(node gonum.Node, parent gonum.Node, deletions uint64, failures uint64) { + bucket := "" + switch t := node.(type) { + case *imagegraph.ImageStreamNode: + bucket = "is" + case *imagegraph.ImageNode: + bucket = "image" + case *imagegraph.ImageComponentNode: + bucket = "component/" + string(t.Type) + if parent == nil { + bucket = "blob" + } + default: + bucket = fmt.Sprintf("other/%T", t) + } + c := buckets[bucket] + c.deletions += deletions + c.failures += failures + c.total += deletions + failures + buckets[bucket] = c + } + + for _, d := range deletions { + count(d.Node, d.Parent, 1, 0) + } + for _, f := range failures { + count(f.Node, f.Parent, 0, 1) + } + + printAndPopBucket := func(name string, desc string) { + cnt, ok := buckets[name] + if ok { + delete(buckets, name) + } + if cnt.total == 0 { + return + } + fmt.Fprintf(w, "%s:\t%d\n", desc, cnt.deletions) + if cnt.failures == 0 { + return + } + // add padding before failures to make it appear subordinate to the line above + for i := 0; i < len(desc)-len("failures"); i++ { + fmt.Fprintf(w, " ") + } + fmt.Fprintf(w, "failures:\t%d\n", cnt.failures) + } + + printAndPopBucket("is", "Image Stream updates") + printAndPopBucket("image", "Image deletions") + printAndPopBucket("blob", "Blob deletions") + printAndPopBucket("component/"+string(imagegraph.ImageComponentTypeManifest), "Image Manifest Link deletions") + printAndPopBucket("component/"+string(imagegraph.ImageComponentTypeConfig), "Image Config Link deletions") + printAndPopBucket("component/"+string(imagegraph.ImageComponentTypeLayer), "Image Layer Link deletions") + + for name := range buckets { + printAndPopBucket(name, fmt.Sprintf("%s deletions", strings.TrimPrefix(name, "other/"))) + } } func (o *PruneImagesOptions) printGraphBuildErrors(errs kutilerrors.Aggregate) { @@ -486,17 +614,11 @@ func (p *describingImageStreamDeleter) UpdateImageStream(stream *imageapi.ImageS } func (p *describingImageStreamDeleter) NotifyImageStreamPrune(stream *imageapi.ImageStream, updatedTags []string, deletedTags []string) { - if !p.headerPrinted { - p.headerPrinted = true - fmt.Fprintln(p.w, "Deleting references from image streams to images ...") - fmt.Fprintln(p.w, "STREAM\tACTION\tTAGS") - } - if len(updatedTags) > 0 { - fmt.Fprintf(p.w, "%s/%s\tUpdated\t%s\n", stream.Namespace, stream.Name, strings.Join(updatedTags, ", ")) + fmt.Fprintf(p.w, "Updating istags %s/%s: %s\n", stream.Namespace, stream.Name, strings.Join(updatedTags, ", ")) } if len(deletedTags) > 0 { - fmt.Fprintf(p.w, "%s/%s\tDeleted\t%s\n", stream.Namespace, stream.Name, strings.Join(deletedTags, ", ")) + fmt.Fprintf(p.w, "Deleting istags %s/%s: %s\n", stream.Namespace, stream.Name, strings.Join(deletedTags, ", ")) } } @@ -512,13 +634,7 @@ type describingImageDeleter struct { var _ imageprune.ImageDeleter = &describingImageDeleter{} func (p *describingImageDeleter) DeleteImage(image *imageapi.Image) error { - if !p.headerPrinted { - p.headerPrinted = true - fmt.Fprintln(p.w, "\nDeleting images from server ...") - fmt.Fprintln(p.w, "IMAGE") - } - - fmt.Fprintf(p.w, "%s\n", image.Name) + fmt.Fprintf(p.w, "Deleting image %s\n", image.Name) if p.delegate == nil { return nil @@ -544,13 +660,7 @@ type describingLayerLinkDeleter struct { var _ imageprune.LayerLinkDeleter = &describingLayerLinkDeleter{} func (p *describingLayerLinkDeleter) DeleteLayerLink(registryClient *http.Client, registryURL *url.URL, repo, name string) error { - if !p.headerPrinted { - p.headerPrinted = true - fmt.Fprintln(p.w, "\nDeleting registry repository layer links ...") - fmt.Fprintln(p.w, "REPO\tLAYER LINK") - } - - fmt.Fprintf(p.w, "%s\t%s\n", repo, name) + fmt.Fprintf(p.w, "Deleting layer link %s in repository %s\n", name, repo) if p.delegate == nil { return nil @@ -576,13 +686,7 @@ type describingBlobDeleter struct { var _ imageprune.BlobDeleter = &describingBlobDeleter{} func (p *describingBlobDeleter) DeleteBlob(registryClient *http.Client, registryURL *url.URL, layer string) error { - if !p.headerPrinted { - p.headerPrinted = true - fmt.Fprintln(p.w, "\nDeleting registry layer blobs ...") - fmt.Fprintln(p.w, "BLOB") - } - - fmt.Fprintf(p.w, "%s\n", layer) + fmt.Fprintf(p.w, "Deleting blob %s\n", layer) if p.delegate == nil { return nil @@ -609,13 +713,7 @@ type describingManifestDeleter struct { var _ imageprune.ManifestDeleter = &describingManifestDeleter{} func (p *describingManifestDeleter) DeleteManifest(registryClient *http.Client, registryURL *url.URL, repo, manifest string) error { - if !p.headerPrinted { - p.headerPrinted = true - fmt.Fprintln(p.w, "\nDeleting registry repository manifest data ...") - fmt.Fprintln(p.w, "REPO\tIMAGE") - } - - fmt.Fprintf(p.w, "%s\t%s\n", repo, manifest) + fmt.Fprintf(p.w, "Deleting manifest link %s in repository %s\n", manifest, repo) if p.delegate == nil { return nil @@ -623,7 +721,7 @@ func (p *describingManifestDeleter) DeleteManifest(registryClient *http.Client, err := p.delegate.DeleteManifest(registryClient, registryURL, repo, manifest) if err != nil { - fmt.Fprintf(p.errOut, "error deleting manifest %s from repository %s: %v\n", manifest, repo, err) + fmt.Fprintf(p.errOut, "error deleting manifest link %s from repository %s: %v\n", manifest, repo, err) } return err @@ -659,6 +757,17 @@ func getClients(f kcmdutil.Factory) (appsclient.AppsInterface, buildclient.Build return appsClient, buildClient, imageClient, kubeClient, err } +func getImageClientFactory(f kcmdutil.Factory) func() (imageclient.ImageInterface, error) { + return func() (imageclient.ImageInterface, error) { + clientConfig, err := f.ClientConfig() + if err != nil { + return nil, err + } + + return imageclient.NewForConfig(clientConfig) + } +} + // getRegistryClient returns a registry client. Note that registryCABundle and registryInsecure=true are // mutually exclusive. If registryInsecure=true is specified, the ca bundle is ignored. func getRegistryClient(clientConfig *restclient.Config, registryCABundle string, registryInsecure bool) (*http.Client, error) { diff --git a/pkg/oc/cli/describe/chaindescriber_test.go b/pkg/oc/cli/describe/chaindescriber_test.go index e310515e7954..2b3952850a71 100644 --- a/pkg/oc/cli/describe/chaindescriber_test.go +++ b/pkg/oc/cli/describe/chaindescriber_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" @@ -275,15 +275,15 @@ func filterByScheme(scheme *runtime.Scheme, in ...runtime.Object) []runtime.Obje } func TestDepthFirst(t *testing.T) { - g := concrete.NewDirectedGraph() - - a := concrete.Node(g.NewNodeID()) - b := concrete.Node(g.NewNodeID()) + g := simple.NewDirectedGraph(1.0, 0.0) + a := simple.Node(g.NewNodeID()) g.AddNode(a) + b := simple.Node(g.NewNodeID()) g.AddNode(b) - g.SetEdge(concrete.Edge{F: a, T: b}, 1) - g.SetEdge(concrete.Edge{F: b, T: a}, 1) + + g.SetEdge(simple.Edge{F: a, T: b}) + g.SetEdge(simple.Edge{F: b, T: a}) count := 0 diff --git a/pkg/oc/graph/appsgraph/edge_test.go b/pkg/oc/graph/appsgraph/edge_test.go index 07b5dbbcd658..d8102243af19 100644 --- a/pkg/oc/graph/appsgraph/edge_test.go +++ b/pkg/oc/graph/appsgraph/edge_test.go @@ -41,7 +41,7 @@ func TestNamespaceEdgeMatching(t *testing.T) { fn("other", g) AddAllDeploymentConfigsDeploymentEdges(g) - if len(g.Edges()) != 4 { + if len(g.Edges()) != 6 { t.Fatal(g) } for _, edge := range g.Edges() { diff --git a/pkg/oc/graph/genericgraph/graph.go b/pkg/oc/graph/genericgraph/graph.go index f44152bd4ba7..89548017efa5 100644 --- a/pkg/oc/graph/genericgraph/graph.go +++ b/pkg/oc/graph/genericgraph/graph.go @@ -7,15 +7,15 @@ import ( "strings" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" "github.com/gonum/graph/encoding/dot" + "github.com/gonum/graph/simple" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/util/sets" ) type Node struct { - concrete.Node + simple.Node UniqueName } @@ -71,19 +71,19 @@ type MutableDirectedEdge interface { } type MutableUniqueGraph interface { - graph.Mutable + graph.DirectedBuilder MutableDirectedEdge UniqueNodeInitializer NodeFinder } type Edge struct { - concrete.Edge + simple.Edge kinds sets.String } -func NewEdge(from, to graph.Node, kinds ...string) Edge { - return Edge{concrete.Edge{F: from, T: to}, sets.NewString(kinds...)} +func NewEdge(from, to graph.Node, weight float64, kinds ...string) Edge { + return Edge{Edge: simple.Edge{F: from, T: to, W: weight}, kinds: sets.NewString(kinds...)} } func (e Edge) Kinds() sets.String { @@ -107,12 +107,12 @@ type GraphDescriber interface { } type Interface interface { - graph.Directed + graph.DirectedBuilder GraphDescriber - MutableUniqueGraph - - Edges() []graph.Edge + MutableDirectedEdge + UniqueNodeInitializer + NodeFinder } type Namer interface { @@ -134,14 +134,14 @@ func (namer) ResourceName(obj interface{}) string { type Graph struct { // the standard graph - graph.Directed + graph.DirectedBuilder // helper methods for switching on the kind and types of the node GraphDescriber // exposes the public interface for adding nodes uniqueNamedGraph // the internal graph object, which allows edges and nodes to be directly added - internal *concrete.DirectedGraph + internal *simple.DirectedGraph } // Graph must implement MutableUniqueGraph @@ -149,10 +149,10 @@ var _ MutableUniqueGraph = Graph{} // New initializes a graph from input to output. func New() Graph { - g := concrete.NewDirectedGraph() + g := simple.NewDirectedGraph(1.0, 0.0) return Graph{ - Directed: g, - GraphDescriber: typedGraph{}, + DirectedBuilder: g, + GraphDescriber: typedGraph{}, uniqueNamedGraph: newUniqueNamedGraph(g), @@ -160,12 +160,6 @@ func New() Graph { } } -// Edges returns all the edges of the graph. Note that the returned set -// will have no specific ordering. -func (g Graph) Edges() []graph.Edge { - return g.internal.Edges() -} - func (g Graph) String() string { ret := "" @@ -189,6 +183,18 @@ func (g Graph) String() string { return ret } +func (g Graph) Edges() []graph.Edge { + return g.internal.Edges() +} + +func (g Graph) RemoveEdge(e graph.Edge) { + g.internal.RemoveEdge(e) +} + +func (g Graph) RemoveNode(node graph.Node) { + g.internal.RemoveNode(node) +} + // ByID is a sorted group of nodes by ID type ByID []graph.Node @@ -198,23 +204,6 @@ func (m ByID) Less(i, j int) bool { return m[i].ID() < m[j].ID() } -// SyntheticNodes returns back the set of nodes that were created in response to edge requests, but did not exist -func (g Graph) SyntheticNodes() []graph.Node { - ret := []graph.Node{} - - nodes := g.Nodes() - sort.Sort(ByID(nodes)) - for _, node := range nodes { - if potentiallySyntheticNode, ok := node.(ExistenceChecker); ok { - if !potentiallySyntheticNode.Found() { - ret = append(ret, node) - } - } - } - - return ret -} - // NodesByKind returns all the nodes of the graph with the provided kinds func (g Graph) NodesByKind(nodeKinds ...string) []graph.Node { ret := []graph.Node{} @@ -229,18 +218,6 @@ func (g Graph) NodesByKind(nodeKinds ...string) []graph.Node { return ret } -// RootNodes returns all the roots of this graph. -func (g Graph) RootNodes() []graph.Node { - roots := []graph.Node{} - for _, n := range g.Nodes() { - if len(g.To(n)) != 0 { - continue - } - roots = append(roots, n) - } - return roots -} - // PredecessorEdges invokes fn with all of the predecessor edges of node that have the specified // edge kind. func (g Graph) PredecessorEdges(node graph.Node, fn EdgeFunc, edgeKinds ...string) { @@ -365,7 +342,7 @@ func (g Graph) AddEdge(from, to graph.Node, edgeKind string) { kinds.Insert(g.EdgeKinds(existingEdge).List()...) } - g.internal.SetEdge(NewEdge(from, to, kinds.List()...), 1.0) + g.internal.SetEdge(NewEdge(from, to, 1.0, kinds.List()...)) } // addEdges adds the specified edges, filtered by the provided edge connection @@ -373,13 +350,13 @@ func (g Graph) AddEdge(from, to graph.Node, edgeKind string) { func (g Graph) addEdges(edges []graph.Edge, fn EdgeFunc) { for _, e := range edges { switch t := e.(type) { - case concrete.WeightedEdge: - if fn(g, t.From(), t.To(), t.Edge.(Edge).Kinds()) { - g.internal.SetEdge(t.Edge.(Edge), t.Cost) - } case Edge: if fn(g, t.From(), t.To(), t.Kinds()) { - g.internal.SetEdge(t, 1.0) + g.internal.SetEdge(t) + } + case simple.Edge: + if fn(g, t.From(), t.To(), sets.NewString()) { + g.internal.SetEdge(t) } default: panic("bad edge") @@ -484,20 +461,6 @@ func (g Graph) SubgraphWithNodes(nodes []graph.Node, fn EdgeFunc) Graph { return out } -// ConnectedEdgeSubgraph creates a new graph that iterates through all edges in the graph -// and includes all edges the provided function returns true for. Nodes not referenced by -// an edge will be dropped unless the function adds them explicitly. -func (g Graph) ConnectedEdgeSubgraph(fn EdgeFunc) Graph { - out := New() - out.addEdges(g.internal.Edges(), fn) - return out -} - -// AllNodes includes all nodes in the graph -func AllNodes(g Interface, node graph.Node) bool { - return true -} - // ExistingDirectEdge returns true if both from and to already exist in the graph and the edge kind is // not ReferencedByEdgeKind (the generic reverse edge kind). This will purge the graph of any // edges created by AddReversedEdge. @@ -528,26 +491,14 @@ func AddReversedEdge(g Interface, from, to graph.Node, edgeKinds sets.String) bo return true } -// AddGraphEdgesTo returns an EdgeFunc that will add the selected edges to the passed -// graph. -func AddGraphEdgesTo(g Interface) EdgeFunc { - return func(_ Interface, from, to graph.Node, edgeKinds sets.String) bool { - for edgeKind := range edgeKinds { - g.AddEdge(from, to, edgeKind) - } - - return false - } -} - type uniqueNamedGraph struct { - graph.Mutable + graph.Builder names map[UniqueName]graph.Node } -func newUniqueNamedGraph(g graph.Mutable) uniqueNamedGraph { +func newUniqueNamedGraph(g graph.Builder) uniqueNamedGraph { return uniqueNamedGraph{ - Mutable: g, + Builder: g, names: make(map[UniqueName]graph.Node), } } @@ -557,7 +508,7 @@ func (g uniqueNamedGraph) FindOrCreate(name UniqueName, fn NodeInitializerFunc) return node, true } id := g.NewNodeID() - node := fn(Node{concrete.Node(id), name}) + node := fn(Node{simple.Node(id), name}) g.names[name] = node g.AddNode(node) return node, false @@ -610,27 +561,16 @@ func (g typedGraph) Kind(node graph.Node) string { func (g typedGraph) EdgeKinds(edge graph.Edge) sets.String { var e Edge switch t := edge.(type) { - case concrete.WeightedEdge: - e = t.Edge.(Edge) case Edge: e = t + case simple.Edge: + e = Edge{Edge: t} default: return sets.NewString(UnknownEdgeKind) } return e.Kinds() } -type NodeSet map[int]struct{} - -func (n NodeSet) Has(id int) bool { - _, ok := n[id] - return ok -} - -func (n NodeSet) Add(id int) { - n[id] = struct{}{} -} - func NodesByKind(g Interface, nodes []graph.Node, kinds ...string) [][]graph.Node { buckets := make(map[string]int) for i, kind := range kinds { diff --git a/pkg/oc/graph/genericgraph/graphview/veneering_test.go b/pkg/oc/graph/genericgraph/graphview/veneering_test.go index 850e8df999fc..445775799d8e 100644 --- a/pkg/oc/graph/genericgraph/graphview/veneering_test.go +++ b/pkg/oc/graph/genericgraph/graphview/veneering_test.go @@ -5,7 +5,7 @@ import ( "time" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kapi "k8s.io/kubernetes/pkg/apis/core" @@ -368,7 +368,7 @@ func TestGraph(t *testing.T) { } } - edge := g.Edge(concrete.Node(bcTestNode.ID()), concrete.Node(istID)) + edge := g.Edge(simple.Node(bcTestNode.ID()), simple.Node(istID)) if edge == nil { t.Fatalf("failed to find edge between %d and %d", bcTestNode.ID(), istID) } @@ -379,7 +379,7 @@ func TestGraph(t *testing.T) { t.Fatalf("expected one edge") } - if e := g.Edge(concrete.Node(bcTestNode.ID()), concrete.Node(istID)); e == nil { + if e := g.Edge(simple.Node(bcTestNode.ID()), simple.Node(istID)); e == nil { t.Errorf("expected edge for %d-%d", bcTestNode.ID(), istID) } diff --git a/pkg/oc/graph/imagegraph/nodes/nodes.go b/pkg/oc/graph/imagegraph/nodes/nodes.go index 61b438635dc1..60ed2b173cbd 100644 --- a/pkg/oc/graph/imagegraph/nodes/nodes.go +++ b/pkg/oc/graph/imagegraph/nodes/nodes.go @@ -198,3 +198,8 @@ func EnsureImageComponentConfigNode(g osgraph.MutableUniqueGraph, name string) g func EnsureImageComponentLayerNode(g osgraph.MutableUniqueGraph, name string) graph.Node { return ensureImageComponentNode(g, name, ImageComponentTypeLayer) } + +// EnsureImageComponentLayerNode adds a graph node for the image layer if it does not already exist. +func EnsureImageComponentManifestNode(g osgraph.MutableUniqueGraph, name string) graph.Node { + return ensureImageComponentNode(g, name, ImageComponentTypeManifest) +} diff --git a/pkg/oc/graph/imagegraph/nodes/nodes_test.go b/pkg/oc/graph/imagegraph/nodes/nodes_test.go new file mode 100644 index 000000000000..c2a9ff0a6768 --- /dev/null +++ b/pkg/oc/graph/imagegraph/nodes/nodes_test.go @@ -0,0 +1,106 @@ +package nodes + +import ( + "testing" + + "github.com/openshift/origin/pkg/oc/admin/prune/imageprune/testutil" + "github.com/openshift/origin/pkg/oc/graph/genericgraph" +) + +const registryHost = "registry.io" + +func TestEnsureImageNode(t *testing.T) { + g := genericgraph.New() + + img1 := testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001") + img2 := testutil.Image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002") + img3 := testutil.Image("sha356:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha356:0000000000000000000000000000000000000000000000000000000000000003") + + node := FindImage(g, img1.Name) + if node != nil { + t.Fatalf("got unexpected image") + } + n1 := EnsureImageNode(g, &img1) + if n1 == nil { + t.Fatalf("failed to create node") + } + node = FindImage(g, img1.Name) + if node == nil { + t.Fatalf("expected node, not nil") + } + if node.Image != &img1 { + t.Fatalf("got unexpected image: %s != %s", node.Image.Name, img1.Name) + } + + n2 := EnsureImageNode(g, &img2) + if n2 == nil { + t.Fatalf("expected node, not nil") + } + if len(g.Nodes()) != 2 { + t.Fatalf("unexpected number of nodes: %d != %d", len(g.Nodes()), 2) + } + + g.RemoveNode(n1) + + // removing node deletes its edges but the image is still known + node = FindImage(g, img1.Name) + if node == nil { + t.Fatalf("removed node can still be found") + } + // but it's no longer iterable + if len(g.Nodes()) != 1 { + t.Fatalf("unexpected number of nodes: %d != %d", len(g.Nodes()), 1) + } + + // add the image1 again + n1 = EnsureImageNode(g, &img1) + if n1 == nil { + t.Fatalf("failed to create node") + } + node = FindImage(g, img1.Name) + if node == nil { + t.Fatalf("expected node, not nil") + } + if node.Image != &img1 { + t.Fatalf("got unexpected image: %s != %s", node.Image.Name, img1.Name) + } + /** + * NOTE: it looks like removed node cannot be re-added + if len(g.Nodes()) != 2 { + t.Fatalf("unexpected number of nodes: %d != %d", len(g.Nodes()), 2) + } + */ + + // re-add the image1 once again + tmp := EnsureImageNode(g, &img1) + if tmp == nil { + t.Fatalf("failed to find the node") + } + if tmp != n1 { + t.Fatalf("got unexpected node: %v != %v", node, n1) + } + + g.RemoveNode(n2) + node = FindImage(g, img2.Name) + if node == nil { + t.Fatalf("removed node can still be found") + } + if len(g.Nodes()) != 0 { + t.Fatalf("unexpected number of nodes: %d != %d", len(g.Nodes()), 0) + } + + n3 := EnsureImageNode(g, &img3) + if n3 == nil { + t.Fatalf("failed to create node") + } + node = FindImage(g, img3.Name) + if node == nil { + t.Fatalf("expected node, not nil") + } + if node.Image != &img3 { + t.Fatalf("got unexpected image: %s != %s", node.Image.Name, img3.Name) + } + if len(g.Nodes()) != 1 { + t.Fatalf("unexpected number of nodes: %d != %d", len(g.Nodes()), 1) + } +} diff --git a/pkg/oc/graph/imagegraph/nodes/types.go b/pkg/oc/graph/imagegraph/nodes/types.go index 5482b757a99d..989f0e3478f7 100644 --- a/pkg/oc/graph/imagegraph/nodes/types.go +++ b/pkg/oc/graph/imagegraph/nodes/types.go @@ -13,8 +13,9 @@ type ImageComponentType string const ( ImageComponentNodeKind = "ImageComponent" - ImageComponentTypeConfig ImageComponentType = `Config` - ImageComponentTypeLayer ImageComponentType = `Layer` + ImageComponentTypeConfig ImageComponentType = `Config` + ImageComponentTypeLayer ImageComponentType = `Layer` + ImageComponentTypeManifest ImageComponentType = `Manifest` ) var ( diff --git a/test/testdata/bootstrappolicy/bootstrap_cluster_roles.yaml b/test/testdata/bootstrappolicy/bootstrap_cluster_roles.yaml index 53d0a6b8154e..099973360db5 100644 --- a/test/testdata/bootstrappolicy/bootstrap_cluster_roles.yaml +++ b/test/testdata/bootstrappolicy/bootstrap_cluster_roles.yaml @@ -1612,6 +1612,7 @@ items: verbs: - get - list + - watch - apiGroups: - "" - image.openshift.io diff --git a/test/testdata/bootstrappolicy/bootstrap_policy_file.yaml b/test/testdata/bootstrappolicy/bootstrap_policy_file.yaml index bd96d95b2a95..21a31a1b8cf7 100644 --- a/test/testdata/bootstrappolicy/bootstrap_policy_file.yaml +++ b/test/testdata/bootstrappolicy/bootstrap_policy_file.yaml @@ -1612,6 +1612,7 @@ items: verbs: - get - list + - watch - apiGroups: - "" - image.openshift.io diff --git a/tools/depcheck/pkg/graph/filter.go b/tools/depcheck/pkg/graph/filter.go index fa9ab6930140..677824064664 100644 --- a/tools/depcheck/pkg/graph/filter.go +++ b/tools/depcheck/pkg/graph/filter.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" ) // FilterPackages receives a graph and a set of packagePrefixes contained within the graph. @@ -70,10 +70,7 @@ func FilterPackages(g *MutableDirectedGraph, packagePrefixes []string) (*Mutable continue } - collapsedGraph.SetEdge(concrete.Edge{ - F: fromNode, - T: toNode, - }, 0) + collapsedGraph.SetEdge(simple.Edge{F: fromNode, T: toNode}) } } diff --git a/tools/depcheck/pkg/graph/filter_test.go b/tools/depcheck/pkg/graph/filter_test.go index bc008274d52b..f48111daaecc 100644 --- a/tools/depcheck/pkg/graph/filter_test.go +++ b/tools/depcheck/pkg/graph/filter_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" ) type testFilterNode struct { @@ -101,10 +101,7 @@ func buildTestGraph(nodes []*testFilterNode) (*MutableDirectedGraph, error) { continue } - g.SetEdge(concrete.Edge{ - F: from, - T: to, - }, 0) + g.SetEdge(simple.Edge{F: from, T: to}) } } diff --git a/tools/depcheck/pkg/graph/flags.go b/tools/depcheck/pkg/graph/flags.go index 12de1720ad9d..9ae668ada16b 100644 --- a/tools/depcheck/pkg/graph/flags.go +++ b/tools/depcheck/pkg/graph/flags.go @@ -13,7 +13,7 @@ import ( "github.com/golang/glog" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" ) // GraphOptions contains values necessary to create a dependency graph. @@ -108,10 +108,7 @@ func (o *GraphOptions) BuildGraph() (*MutableDirectedGraph, error) { continue } - g.SetEdge(concrete.Edge{ - F: from, - T: to, - }, 0) + g.SetEdge(simple.Edge{F: from, T: to}) } } diff --git a/tools/depcheck/pkg/graph/graph.go b/tools/depcheck/pkg/graph/graph.go index 9a78629a043d..f337b8d8a451 100644 --- a/tools/depcheck/pkg/graph/graph.go +++ b/tools/depcheck/pkg/graph/graph.go @@ -5,8 +5,8 @@ import ( "strings" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" "github.com/gonum/graph/encoding/dot" + "github.com/gonum/graph/simple" ) type Node struct { @@ -48,14 +48,14 @@ func labelNameForNode(importPath string) string { func NewMutableDirectedGraph(roots []string) *MutableDirectedGraph { return &MutableDirectedGraph{ - DirectedGraph: concrete.NewDirectedGraph(), + DirectedGraph: simple.NewDirectedGraph(1.0, 0.0), nodesByName: make(map[string]graph.Node), rootNodeNames: roots, } } type MutableDirectedGraph struct { - *concrete.DirectedGraph + *simple.DirectedGraph nodesByName map[string]graph.Node rootNodeNames []string @@ -159,10 +159,7 @@ func (g *MutableDirectedGraph) Copy() *MutableDirectedGraph { continue } - newGraph.SetEdge(concrete.Edge{ - F: n, - T: to, - }, 0) + newGraph.SetEdge(simple.Edge{F: n, T: to}) } } diff --git a/tools/depcheck/pkg/graph/graph_test.go b/tools/depcheck/pkg/graph/graph_test.go index 171e61fdfa24..13a4afa47ce7 100644 --- a/tools/depcheck/pkg/graph/graph_test.go +++ b/tools/depcheck/pkg/graph/graph_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" ) type testNode struct { @@ -75,10 +75,7 @@ func addNodes(g *MutableDirectedGraph) error { return fmt.Errorf("attempt to add duplicate edge between %q and %q", n.realNode.UniqueName, childName) } - g.SetEdge(concrete.Edge{ - F: n.realNode, - T: childNode, - }, 0) + g.SetEdge(simple.Edge{F: n.realNode, T: childNode}) } } return nil diff --git a/vendor/github.com/gonum/graph/.travis.yml b/vendor/github.com/gonum/graph/.travis.yml index a5e9aa1530f8..9cfc4e4b83b1 100644 --- a/vendor/github.com/gonum/graph/.travis.yml +++ b/vendor/github.com/gonum/graph/.travis.yml @@ -1,10 +1,12 @@ +sudo: false + language: go # Versions of go that are explicitly supported by gonum. go: - - 1.5beta1 - - 1.3.3 - - 1.4.2 + - 1.5.4 + - 1.6.3 + - 1.7.3 # Required for coverage. before_install: @@ -16,14 +18,7 @@ before_install: script: - go get -d -t -v ./... - go build -v ./... - - go test -v ./... - - diff <(gofmt -d .) <("") + - go test -v -a ./... + - go test -v -a -tags appengine ./... + - test -z "$(gofmt -d .)" - if [[ $TRAVIS_SECURE_ENV_VARS = "true" ]]; then bash ./.travis/test-coverage.sh; fi - -notifications: - email: - recipients: - - jragonmiris@gmail.com - on_success: change - on_failure: always - diff --git a/vendor/github.com/gonum/graph/.travis/test-coverage.sh b/vendor/github.com/gonum/graph/.travis/test-coverage.sh index 854f8c3aa624..7df8aa6a9bc6 100755 --- a/vendor/github.com/gonum/graph/.travis/test-coverage.sh +++ b/vendor/github.com/gonum/graph/.travis/test-coverage.sh @@ -1,43 +1,35 @@ #!/bin/bash -# based on http://stackoverflow.com/questions/21126011/is-it-possible-to-post-coverage-for-multiple-packages-to-coveralls -# with script found at https://github.com/gopns/gopns/blob/master/test-coverage.sh +PROFILE_OUT=$PWD/profile.out +ACC_OUT=$PWD/acc.out -echo "mode: set" > acc.out -returnval=`go test -v -coverprofile=profile.out` -echo ${returnval} -if [[ ${returnval} != *FAIL* ]] -then - if [ -f profile.out ] - then - cat profile.out | grep -v "mode: set" >> acc.out - fi -else - exit 1 -fi +testCover() { + # set the return value to 0 (succesful) + retval=0 + # get the directory to check from the parameter. Default to '.' + d=${1:-.} + # skip if there are no Go files here + ls $d/*.go &> /dev/null || return $retval + # switch to the directory to check + pushd $d > /dev/null + # create the coverage profile + coverageresult=`go test -v -coverprofile=$PROFILE_OUT` + # output the result so we can check the shell output + echo ${coverageresult} + # append the results to acc.out if coverage didn't fail, else set the retval to 1 (failed) + ( [[ ${coverageresult} == *FAIL* ]] && retval=1 ) || ( [ -f $PROFILE_OUT ] && grep -v "mode: set" $PROFILE_OUT >> $ACC_OUT ) + # return to our working dir + popd > /dev/null + # return our return value + return $retval +} -for Dir in $(find ./* -maxdepth 10 -type d ); -do - if ls $Dir/*.go &> /dev/null; - then - echo $Dir - returnval=`go test -v -coverprofile=profile.out $Dir` - echo ${returnval} - if [[ ${returnval} != *FAIL* ]] - then - if [ -f profile.out ] - then - cat profile.out | grep -v "mode: set" >> acc.out - fi - else - exit 1 - fi - fi -done -if [ -n "$COVERALLS_TOKEN" ] -then - $HOME/gopath/bin/goveralls -coverprofile=acc.out -service=travis-ci -repotoken $COVERALLS_TOKEN -fi +# Init acc.out +echo "mode: set" > $ACC_OUT + +# Run test coverage on all directories containing go files +find . -maxdepth 10 -type d | while read d; do testCover $d || exit; done + +# Upload the coverage profile to coveralls.io +[ -n "$COVERALLS_TOKEN" ] && goveralls -coverprofile=$ACC_OUT -service=travis-ci -repotoken $COVERALLS_TOKEN -rm -rf ./profile.out -rm -rf ./acc.out diff --git a/vendor/github.com/gonum/graph/README.md b/vendor/github.com/gonum/graph/README.md index 3c4c17962815..469a8f60cae5 100644 --- a/vendor/github.com/gonum/graph/README.md +++ b/vendor/github.com/gonum/graph/README.md @@ -1,4 +1,4 @@ -# Gonum Graph [![Build Status](https://travis-ci.org/gonum/graph.svg?branch=master)](https://travis-ci.org/gonum/graph) [![Coverage Status](https://img.shields.io/coveralls/gonum/graph.svg)](https://coveralls.io/r/gonum/graph?branch=master) +# Gonum Graph [![Build Status](https://travis-ci.org/gonum/graph.svg?branch=master)](https://travis-ci.org/gonum/graph) [![Coverage Status](https://coveralls.io/repos/gonum/graph/badge.svg?branch=master&service=github)](https://coveralls.io/github/gonum/graph?branch=master) [![GoDoc](https://godoc.org/github.com/gonum/graph?status.svg)](https://godoc.org/github.com/gonum/graph) This is a generalized graph package for the Go language. It aims to provide a clean, transparent API for common algorithms on arbitrary graphs such as finding the graph's strongly connected components, dominators, or searces. diff --git a/vendor/github.com/gonum/graph/concrete/concrete.go b/vendor/github.com/gonum/graph/concrete/concrete.go deleted file mode 100644 index 4b272a76c756..000000000000 --- a/vendor/github.com/gonum/graph/concrete/concrete.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright ©2014 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 concrete - -// TODO(anyone) Package level documentation for this describing the overall -// reason for the package and a summary for the provided types. diff --git a/vendor/github.com/gonum/graph/concrete/dense_directed_matrix.go b/vendor/github.com/gonum/graph/concrete/dense_directed_matrix.go deleted file mode 100644 index 5e72db68424c..000000000000 --- a/vendor/github.com/gonum/graph/concrete/dense_directed_matrix.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright ©2014 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 concrete - -import ( - "github.com/gonum/graph" - "github.com/gonum/matrix/mat64" -) - -// DirectedDenseGraph represents a graph such that all IDs are in a contiguous -// block from 0 to n-1. -type DirectedDenseGraph struct { - absent float64 - mat *mat64.Dense -} - -// NewDirectedDenseGraph creates a directed dense graph with n nodes. -// If passable is true all pairs of nodes will be connected by an edge -// with unit cost, otherwise every node will start unconnected with -// the cost specified by absent. -func NewDirectedDenseGraph(n int, passable bool, absent float64) *DirectedDenseGraph { - mat := make([]float64, n*n) - v := 1. - if !passable { - v = absent - } - for i := range mat { - mat[i] = v - } - return &DirectedDenseGraph{mat: mat64.NewDense(n, n, mat), absent: absent} -} - -func (g *DirectedDenseGraph) Has(n graph.Node) bool { - id := n.ID() - r, _ := g.mat.Dims() - return 0 <= id && id < r -} - -func (g *DirectedDenseGraph) Nodes() []graph.Node { - r, _ := g.mat.Dims() - nodes := make([]graph.Node, r) - for i := 0; i < r; i++ { - nodes[i] = Node(i) - } - return nodes -} - -func (g *DirectedDenseGraph) Edges() []graph.Edge { - var edges []graph.Edge - r, _ := g.mat.Dims() - for i := 0; i < r; i++ { - for j := 0; j < r; j++ { - if i == j { - continue - } - if !isSame(g.mat.At(i, j), g.absent) { - edges = append(edges, Edge{Node(i), Node(j)}) - } - } - } - return edges -} - -func (g *DirectedDenseGraph) From(n graph.Node) []graph.Node { - var neighbors []graph.Node - id := n.ID() - _, c := g.mat.Dims() - for j := 0; j < c; j++ { - if j == id { - continue - } - if !isSame(g.mat.At(id, j), g.absent) { - neighbors = append(neighbors, Node(j)) - } - } - return neighbors -} - -func (g *DirectedDenseGraph) To(n graph.Node) []graph.Node { - var neighbors []graph.Node - id := n.ID() - r, _ := g.mat.Dims() - for i := 0; i < r; i++ { - if i == id { - continue - } - if !isSame(g.mat.At(i, id), g.absent) { - neighbors = append(neighbors, Node(i)) - } - } - return neighbors -} - -func (g *DirectedDenseGraph) HasEdge(x, y graph.Node) bool { - xid := x.ID() - yid := y.ID() - return xid != yid && (!isSame(g.mat.At(xid, yid), g.absent) || !isSame(g.mat.At(yid, xid), g.absent)) -} - -func (g *DirectedDenseGraph) Edge(u, v graph.Node) graph.Edge { - if g.HasEdge(u, v) { - return Edge{u, v} - } - return nil -} - -func (g *DirectedDenseGraph) HasEdgeFromTo(u, v graph.Node) bool { - uid := u.ID() - vid := v.ID() - return uid != vid && !isSame(g.mat.At(uid, vid), g.absent) -} - -func (g *DirectedDenseGraph) Weight(e graph.Edge) float64 { - return g.mat.At(e.From().ID(), e.To().ID()) -} - -func (g *DirectedDenseGraph) SetEdgeWeight(e graph.Edge, weight float64) { - fid := e.From().ID() - tid := e.To().ID() - if fid == tid { - panic("concrete: set edge cost of illegal edge") - } - g.mat.Set(fid, tid, weight) -} - -func (g *DirectedDenseGraph) RemoveEdge(e graph.Edge) { - g.mat.Set(e.From().ID(), e.To().ID(), g.absent) -} - -func (g *DirectedDenseGraph) Matrix() mat64.Matrix { - // Prevent alteration of dimensions of the returned matrix. - m := *g.mat - return &m -} diff --git a/vendor/github.com/gonum/graph/concrete/dense_undirected_matrix.go b/vendor/github.com/gonum/graph/concrete/dense_undirected_matrix.go deleted file mode 100644 index d5aca0285267..000000000000 --- a/vendor/github.com/gonum/graph/concrete/dense_undirected_matrix.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright ©2014 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 concrete - -import ( - "github.com/gonum/graph" - "github.com/gonum/matrix/mat64" -) - -// UndirectedDenseGraph represents a graph such that all IDs are in a contiguous -// block from 0 to n-1. -type UndirectedDenseGraph struct { - absent float64 - mat *mat64.SymDense -} - -// NewUndirectedDenseGraph creates an undirected dense graph with n nodes. -// If passable is true all pairs of nodes will be connected by an edge -// with unit cost, otherwise every node will start unconnected with -// the cost specified by absent. -func NewUndirectedDenseGraph(n int, passable bool, absent float64) *UndirectedDenseGraph { - mat := make([]float64, n*n) - v := 1. - if !passable { - v = absent - } - for i := range mat { - mat[i] = v - } - return &UndirectedDenseGraph{mat: mat64.NewSymDense(n, mat), absent: absent} -} - -func (g *UndirectedDenseGraph) Has(n graph.Node) bool { - id := n.ID() - r := g.mat.Symmetric() - return 0 <= id && id < r -} - -func (g *UndirectedDenseGraph) Nodes() []graph.Node { - r := g.mat.Symmetric() - nodes := make([]graph.Node, r) - for i := 0; i < r; i++ { - nodes[i] = Node(i) - } - return nodes -} - -func (g *UndirectedDenseGraph) Edges() []graph.Edge { - var edges []graph.Edge - r, _ := g.mat.Dims() - for i := 0; i < r; i++ { - for j := i + 1; j < r; j++ { - if !isSame(g.mat.At(i, j), g.absent) { - edges = append(edges, Edge{Node(i), Node(j)}) - } - } - } - return edges -} - -func (g *UndirectedDenseGraph) Degree(n graph.Node) int { - id := n.ID() - var deg int - r := g.mat.Symmetric() - for i := 0; i < r; i++ { - if i == id { - continue - } - if !isSame(g.mat.At(id, i), g.absent) { - deg++ - } - } - return deg -} - -func (g *UndirectedDenseGraph) From(n graph.Node) []graph.Node { - var neighbors []graph.Node - id := n.ID() - r := g.mat.Symmetric() - for i := 0; i < r; i++ { - if i == id { - continue - } - if !isSame(g.mat.At(id, i), g.absent) { - neighbors = append(neighbors, Node(i)) - } - } - return neighbors -} - -func (g *UndirectedDenseGraph) HasEdge(u, v graph.Node) bool { - uid := u.ID() - vid := v.ID() - return uid != vid && !isSame(g.mat.At(uid, vid), g.absent) -} - -func (g *UndirectedDenseGraph) Edge(u, v graph.Node) graph.Edge { - return g.EdgeBetween(u, v) -} - -func (g *UndirectedDenseGraph) EdgeBetween(u, v graph.Node) graph.Edge { - if g.HasEdge(u, v) { - return Edge{u, v} - } - return nil -} - -func (g *UndirectedDenseGraph) Weight(e graph.Edge) float64 { - return g.mat.At(e.From().ID(), e.To().ID()) -} - -func (g *UndirectedDenseGraph) SetEdgeWeight(e graph.Edge, weight float64) { - fid := e.From().ID() - tid := e.To().ID() - if fid == tid { - panic("concrete: set edge cost of illegal edge") - } - g.mat.SetSym(fid, tid, weight) -} - -func (g *UndirectedDenseGraph) RemoveEdge(e graph.Edge) { - g.mat.SetSym(e.From().ID(), e.To().ID(), g.absent) -} - -func (g *UndirectedDenseGraph) Matrix() mat64.Matrix { - // Prevent alteration of dimensions of the returned matrix. - m := *g.mat - return &m -} diff --git a/vendor/github.com/gonum/graph/concrete/densegraph_test.go b/vendor/github.com/gonum/graph/concrete/densegraph_test.go deleted file mode 100644 index e5531059c349..000000000000 --- a/vendor/github.com/gonum/graph/concrete/densegraph_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright ©2014 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 concrete_test - -import ( - "math" - "sort" - "testing" - - "github.com/gonum/graph" - "github.com/gonum/graph/concrete" -) - -var ( - _ graph.Graph = (*concrete.UndirectedDenseGraph)(nil) - _ graph.Directed = (*concrete.DirectedDenseGraph)(nil) -) - -func TestBasicDenseImpassable(t *testing.T) { - dg := concrete.NewUndirectedDenseGraph(5, false, math.Inf(1)) - if dg == nil { - t.Fatal("Directed graph could not be made") - } - - for i := 0; i < 5; i++ { - if !dg.Has(concrete.Node(i)) { - t.Errorf("Node that should exist doesn't: %d", i) - } - - if degree := dg.Degree(concrete.Node(i)); degree != 0 { - t.Errorf("Node in impassable graph has a neighbor. Node: %d Degree: %d", i, degree) - } - } - - for i := 5; i < 10; i++ { - if dg.Has(concrete.Node(i)) { - t.Errorf("Node exists that shouldn't: %d", i) - } - } -} - -func TestBasicDensePassable(t *testing.T) { - dg := concrete.NewUndirectedDenseGraph(5, true, math.Inf(1)) - if dg == nil { - t.Fatal("Directed graph could not be made") - } - - for i := 0; i < 5; i++ { - if !dg.Has(concrete.Node(i)) { - t.Errorf("Node that should exist doesn't: %d", i) - } - - if degree := dg.Degree(concrete.Node(i)); degree != 4 { - t.Errorf("Node in passable graph missing neighbors. Node: %d Degree: %d", i, degree) - } - } - - for i := 5; i < 10; i++ { - if dg.Has(concrete.Node(i)) { - t.Errorf("Node exists that shouldn't: %d", i) - } - } -} - -func TestDirectedDenseAddRemove(t *testing.T) { - dg := concrete.NewDirectedDenseGraph(10, false, math.Inf(1)) - dg.SetEdgeWeight(concrete.Edge{concrete.Node(0), concrete.Node(2)}, 1) - - if neighbors := dg.From(concrete.Node(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 || - dg.Edge(concrete.Node(0), concrete.Node(2)) == nil { - t.Errorf("Adding edge didn't create successor") - } - - dg.RemoveEdge(concrete.Edge{concrete.Node(0), concrete.Node(2)}) - - if neighbors := dg.From(concrete.Node(0)); len(neighbors) != 0 || dg.Edge(concrete.Node(0), concrete.Node(2)) != nil { - t.Errorf("Removing edge didn't properly remove successor") - } - - if neighbors := dg.To(concrete.Node(2)); len(neighbors) != 0 || dg.Edge(concrete.Node(0), concrete.Node(2)) != nil { - t.Errorf("Removing directed edge wrongly kept predecessor") - } - - dg.SetEdgeWeight(concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2) - // I figure we've torture tested From/To at this point - // so we'll just use the bool functions now - if dg.Edge(concrete.Node(0), concrete.Node(2)) == nil { - t.Error("Adding directed edge didn't change successor back") - } else if c1, c2 := dg.Weight(concrete.Edge{concrete.Node(2), concrete.Node(0)}), dg.Weight(concrete.Edge{concrete.Node(0), concrete.Node(2)}); math.Abs(c1-c2) < .000001 { - t.Error("Adding directed edge affected cost in undirected manner") - } -} - -func TestUndirectedDenseAddRemove(t *testing.T) { - dg := concrete.NewUndirectedDenseGraph(10, false, math.Inf(1)) - dg.SetEdgeWeight(concrete.Edge{concrete.Node(0), concrete.Node(2)}, 1) - - if neighbors := dg.From(concrete.Node(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 || - dg.EdgeBetween(concrete.Node(0), concrete.Node(2)) == nil { - t.Errorf("Couldn't add neighbor") - } - - if neighbors := dg.From(concrete.Node(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 || - dg.EdgeBetween(concrete.Node(2), concrete.Node(0)) == nil { - t.Errorf("Adding an undirected neighbor didn't add it reciprocally") - } -} - -type nodeSorter []graph.Node - -func (ns nodeSorter) Len() int { - return len(ns) -} - -func (ns nodeSorter) Swap(i, j int) { - ns[i], ns[j] = ns[j], ns[i] -} - -func (ns nodeSorter) Less(i, j int) bool { - return ns[i].ID() < ns[j].ID() -} - -func TestDenseLists(t *testing.T) { - dg := concrete.NewDirectedDenseGraph(15, true, math.Inf(1)) - nodes := nodeSorter(dg.Nodes()) - - if len(nodes) != 15 { - t.Fatalf("Wrong number of nodes") - } - - sort.Sort(nodes) - - for i, node := range dg.Nodes() { - if i != node.ID() { - t.Errorf("Node list doesn't return properly id'd nodes") - } - } - - edges := dg.Edges() - if len(edges) != 15*14 { - t.Errorf("Improper number of edges for passable dense graph") - } - - dg.RemoveEdge(concrete.Edge{concrete.Node(12), concrete.Node(11)}) - edges = dg.Edges() - if len(edges) != (15*14)-1 { - t.Errorf("Removing edge didn't affect edge listing properly") - } -} diff --git a/vendor/github.com/gonum/graph/concrete/directed.go b/vendor/github.com/gonum/graph/concrete/directed.go deleted file mode 100644 index 9366e6a51439..000000000000 --- a/vendor/github.com/gonum/graph/concrete/directed.go +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright ©2014 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 concrete - -import ( - "fmt" - - "github.com/gonum/graph" -) - -// A Directed graph is a highly generalized MutableDirectedGraph. -// -// In most cases it's likely more desireable to use a graph specific to your -// problem domain. -type DirectedGraph struct { - successors map[int]map[int]WeightedEdge - predecessors map[int]map[int]WeightedEdge - nodeMap map[int]graph.Node - - // Add/remove convenience variables - maxID int - freeMap map[int]struct{} -} - -func NewDirectedGraph() *DirectedGraph { - return &DirectedGraph{ - successors: make(map[int]map[int]WeightedEdge), - predecessors: make(map[int]map[int]WeightedEdge), - nodeMap: make(map[int]graph.Node), - maxID: 0, - freeMap: make(map[int]struct{}), - } -} - -func (g *DirectedGraph) NewNodeID() int { - if g.maxID != maxInt { - g.maxID++ - return g.maxID - } - - // Implicitly checks if len(g.freeMap) == 0 - for id := range g.freeMap { - return id - } - - // I cannot foresee this ever happening, but just in case - if len(g.nodeMap) == maxInt { - panic("cannot allocate node: graph too large") - } - - for i := 0; i < maxInt; i++ { - if _, ok := g.nodeMap[i]; !ok { - return i - } - } - - // Should not happen. - panic("cannot allocate node id: no free id found") -} - -// Adds a node to the graph. Implementation note: if you add a node close to or at -// the max int on your machine NewNode will become slower. -func (g *DirectedGraph) AddNode(n graph.Node) { - if _, exists := g.nodeMap[n.ID()]; exists { - panic(fmt.Sprintf("concrete: node ID collision: %d", n.ID())) - } - g.nodeMap[n.ID()] = n - g.successors[n.ID()] = make(map[int]WeightedEdge) - g.predecessors[n.ID()] = make(map[int]WeightedEdge) - - delete(g.freeMap, n.ID()) - g.maxID = max(g.maxID, n.ID()) -} - -func (g *DirectedGraph) SetEdge(e graph.Edge, cost float64) { - var ( - from = e.From() - fid = from.ID() - to = e.To() - tid = to.ID() - ) - - if fid == tid { - panic("concrete: adding self edge") - } - - if !g.Has(from) { - g.AddNode(from) - } - - if !g.Has(to) { - g.AddNode(to) - } - - g.successors[fid][tid] = WeightedEdge{Edge: e, Cost: cost} - g.predecessors[tid][fid] = WeightedEdge{Edge: e, Cost: cost} -} - -func (g *DirectedGraph) RemoveNode(n graph.Node) { - if _, ok := g.nodeMap[n.ID()]; !ok { - return - } - delete(g.nodeMap, n.ID()) - - for succ := range g.successors[n.ID()] { - delete(g.predecessors[succ], n.ID()) - } - delete(g.successors, n.ID()) - - for pred := range g.predecessors[n.ID()] { - delete(g.successors[pred], n.ID()) - } - delete(g.predecessors, n.ID()) - - g.maxID-- // Fun facts: even if this ID doesn't exist this still works! - g.freeMap[n.ID()] = struct{}{} -} - -func (g *DirectedGraph) RemoveEdge(e graph.Edge) { - from, to := e.From(), e.To() - if _, ok := g.nodeMap[from.ID()]; !ok { - return - } else if _, ok := g.nodeMap[to.ID()]; !ok { - return - } - - delete(g.successors[from.ID()], to.ID()) - delete(g.predecessors[to.ID()], from.ID()) -} - -func (g *DirectedGraph) EmptyGraph() { - g.successors = make(map[int]map[int]WeightedEdge) - g.predecessors = make(map[int]map[int]WeightedEdge) - g.nodeMap = make(map[int]graph.Node) -} - -/* Graph implementation */ - -func (g *DirectedGraph) From(n graph.Node) []graph.Node { - if _, ok := g.successors[n.ID()]; !ok { - return nil - } - - successors := make([]graph.Node, len(g.successors[n.ID()])) - i := 0 - for succ := range g.successors[n.ID()] { - successors[i] = g.nodeMap[succ] - i++ - } - - return successors -} - -func (g *DirectedGraph) HasEdge(x, y graph.Node) bool { - xid := x.ID() - yid := y.ID() - if _, ok := g.nodeMap[xid]; !ok { - return false - } - if _, ok := g.nodeMap[yid]; !ok { - return false - } - if _, ok := g.successors[xid][yid]; ok { - return true - } - _, ok := g.successors[yid][xid] - return ok -} - -func (g *DirectedGraph) Edge(u, v graph.Node) graph.Edge { - if _, ok := g.nodeMap[u.ID()]; !ok { - return nil - } - if _, ok := g.nodeMap[v.ID()]; !ok { - return nil - } - edge, ok := g.successors[u.ID()][v.ID()] - if !ok { - return nil - } - return edge.Edge -} - -func (g *DirectedGraph) HasEdgeFromTo(u, v graph.Node) bool { - if _, ok := g.nodeMap[u.ID()]; !ok { - return false - } - if _, ok := g.nodeMap[v.ID()]; !ok { - return false - } - if _, ok := g.successors[u.ID()][v.ID()]; !ok { - return false - } - return true -} - -func (g *DirectedGraph) To(n graph.Node) []graph.Node { - if _, ok := g.successors[n.ID()]; !ok { - return nil - } - - predecessors := make([]graph.Node, len(g.predecessors[n.ID()])) - i := 0 - for succ := range g.predecessors[n.ID()] { - predecessors[i] = g.nodeMap[succ] - i++ - } - - return predecessors -} - -func (g *DirectedGraph) Node(id int) graph.Node { - return g.nodeMap[id] -} - -func (g *DirectedGraph) Has(n graph.Node) bool { - _, ok := g.nodeMap[n.ID()] - - return ok -} - -func (g *DirectedGraph) Degree(n graph.Node) int { - if _, ok := g.nodeMap[n.ID()]; !ok { - return 0 - } - - return len(g.successors[n.ID()]) + len(g.predecessors[n.ID()]) -} - -func (g *DirectedGraph) Nodes() []graph.Node { - nodes := make([]graph.Node, len(g.successors)) - i := 0 - for _, n := range g.nodeMap { - nodes[i] = n - i++ - } - - return nodes -} - -func (g *DirectedGraph) Weight(e graph.Edge) float64 { - if s, ok := g.successors[e.From().ID()]; ok { - if we, ok := s[e.To().ID()]; ok { - return we.Cost - } - } - return inf -} - -func (g *DirectedGraph) Edges() []graph.Edge { - edgeList := make([]graph.Edge, 0, len(g.successors)) - edgeMap := make(map[int]map[int]struct{}, len(g.successors)) - for n, succMap := range g.successors { - edgeMap[n] = make(map[int]struct{}, len(succMap)) - for succ, edge := range succMap { - if doneMap, ok := edgeMap[succ]; ok { - if _, ok := doneMap[n]; ok { - continue - } - } - edgeList = append(edgeList, edge) - edgeMap[n][succ] = struct{}{} - } - } - - return edgeList -} diff --git a/vendor/github.com/gonum/graph/concrete/directed_test.go b/vendor/github.com/gonum/graph/concrete/directed_test.go deleted file mode 100644 index feaf58376bbe..000000000000 --- a/vendor/github.com/gonum/graph/concrete/directed_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright ©2014 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 concrete_test - -import ( - "testing" - - "github.com/gonum/graph" - "github.com/gonum/graph/concrete" -) - -var _ graph.Graph = &concrete.DirectedGraph{} -var _ graph.Directed = &concrete.DirectedGraph{} -var _ graph.Directed = &concrete.DirectedGraph{} - -// Tests Issue #27 -func TestEdgeOvercounting(t *testing.T) { - g := generateDummyGraph() - - if neigh := g.From(concrete.Node(concrete.Node(2))); len(neigh) != 2 { - t.Errorf("Node 2 has incorrect number of neighbors got neighbors %v (count %d), expected 2 neighbors {0,1}", neigh, len(neigh)) - } -} - -func generateDummyGraph() *concrete.DirectedGraph { - nodes := [4]struct{ srcId, targetId int }{ - {2, 1}, - {1, 0}, - {2, 0}, - {0, 2}, - } - - g := concrete.NewDirectedGraph() - - for _, n := range nodes { - g.SetEdge(concrete.Edge{concrete.Node(n.srcId), concrete.Node(n.targetId)}, 1) - } - - return g -} diff --git a/vendor/github.com/gonum/graph/concrete/undirected.go b/vendor/github.com/gonum/graph/concrete/undirected.go deleted file mode 100644 index 0bc13b71f278..000000000000 --- a/vendor/github.com/gonum/graph/concrete/undirected.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright ©2014 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 concrete - -import ( - "fmt" - - "github.com/gonum/graph" -) - -// A simple int alias. -type Node int - -func (n Node) ID() int { - return int(n) -} - -// Just a collection of two nodes -type Edge struct { - F, T graph.Node -} - -func (e Edge) From() graph.Node { - return e.F -} - -func (e Edge) To() graph.Node { - return e.T -} - -type WeightedEdge struct { - graph.Edge - Cost float64 -} - -// A GonumGraph is a very generalized graph that can handle an arbitrary number of vertices and -// edges -- as well as act as either directed or undirected. -// -// Internally, it uses a map of successors AND predecessors, to speed up some operations (such as -// getting all successors/predecessors). It also speeds up things like adding edges (assuming both -// edges exist). -// -// However, its generality is also its weakness (and partially a flaw in needing to satisfy -// MutableGraph). For most purposes, creating your own graph is probably better. For instance, -// see TileGraph for an example of an immutable 2D grid of tiles that also implements the Graph -// interface, but would be more suitable if all you needed was a simple undirected 2D grid. -type Graph struct { - neighbors map[int]map[int]WeightedEdge - nodeMap map[int]graph.Node - - // Node add/remove convenience vars - maxID int - freeMap map[int]struct{} -} - -func NewGraph() *Graph { - return &Graph{ - neighbors: make(map[int]map[int]WeightedEdge), - nodeMap: make(map[int]graph.Node), - maxID: 0, - freeMap: make(map[int]struct{}), - } -} - -func (g *Graph) NewNodeID() int { - if g.maxID != maxInt { - g.maxID++ - return g.maxID - } - - // Implicitly checks if len(g.freeMap) == 0 - for id := range g.freeMap { - return id - } - - // I cannot foresee this ever happening, but just in case, we check. - if len(g.nodeMap) == maxInt { - panic("cannot allocate node: graph too large") - } - - for i := 0; i < maxInt; i++ { - if _, ok := g.nodeMap[i]; !ok { - return i - } - } - - // Should not happen. - panic("cannot allocate node id: no free id found") -} - -func (g *Graph) AddNode(n graph.Node) { - if _, exists := g.nodeMap[n.ID()]; exists { - panic(fmt.Sprintf("concrete: node ID collision: %d", n.ID())) - } - g.nodeMap[n.ID()] = n - g.neighbors[n.ID()] = make(map[int]WeightedEdge) - - delete(g.freeMap, n.ID()) - g.maxID = max(g.maxID, n.ID()) -} - -func (g *Graph) SetEdge(e graph.Edge, cost float64) { - var ( - from = e.From() - fid = from.ID() - to = e.To() - tid = to.ID() - ) - - if fid == tid { - panic("concrete: adding self edge") - } - - if !g.Has(from) { - g.AddNode(from) - } - - if !g.Has(to) { - g.AddNode(to) - } - - g.neighbors[fid][tid] = WeightedEdge{Edge: e, Cost: cost} - g.neighbors[tid][fid] = WeightedEdge{Edge: e, Cost: cost} -} - -func (g *Graph) RemoveNode(n graph.Node) { - if _, ok := g.nodeMap[n.ID()]; !ok { - return - } - delete(g.nodeMap, n.ID()) - - for neigh := range g.neighbors[n.ID()] { - delete(g.neighbors[neigh], n.ID()) - } - delete(g.neighbors, n.ID()) - - if g.maxID != 0 && n.ID() == g.maxID { - g.maxID-- - } - g.freeMap[n.ID()] = struct{}{} -} - -func (g *Graph) RemoveEdge(e graph.Edge) { - from, to := e.From(), e.To() - if _, ok := g.nodeMap[from.ID()]; !ok { - return - } else if _, ok := g.nodeMap[to.ID()]; !ok { - return - } - - delete(g.neighbors[from.ID()], to.ID()) - delete(g.neighbors[to.ID()], from.ID()) -} - -func (g *Graph) EmptyGraph() { - g.neighbors = make(map[int]map[int]WeightedEdge) - g.nodeMap = make(map[int]graph.Node) -} - -/* Graph implementation */ - -func (g *Graph) From(n graph.Node) []graph.Node { - if !g.Has(n) { - return nil - } - - neighbors := make([]graph.Node, len(g.neighbors[n.ID()])) - i := 0 - for id := range g.neighbors[n.ID()] { - neighbors[i] = g.nodeMap[id] - i++ - } - - return neighbors -} - -func (g *Graph) HasEdge(n, neigh graph.Node) bool { - _, ok := g.neighbors[n.ID()][neigh.ID()] - return ok -} - -func (g *Graph) Edge(u, v graph.Node) graph.Edge { - return g.EdgeBetween(u, v) -} - -func (g *Graph) EdgeBetween(u, v graph.Node) graph.Edge { - // We don't need to check if neigh exists because - // it's implicit in the neighbors access. - if !g.Has(u) { - return nil - } - - return g.neighbors[u.ID()][v.ID()].Edge -} - -func (g *Graph) Node(id int) graph.Node { - return g.nodeMap[id] -} - -func (g *Graph) Has(n graph.Node) bool { - _, ok := g.nodeMap[n.ID()] - - return ok -} - -func (g *Graph) Nodes() []graph.Node { - nodes := make([]graph.Node, len(g.nodeMap)) - i := 0 - for _, n := range g.nodeMap { - nodes[i] = n - i++ - } - - return nodes -} - -func (g *Graph) Weight(e graph.Edge) float64 { - if n, ok := g.neighbors[e.From().ID()]; ok { - if we, ok := n[e.To().ID()]; ok { - return we.Cost - } - } - return inf -} - -func (g *Graph) Edges() []graph.Edge { - m := make(map[WeightedEdge]struct{}) - toReturn := make([]graph.Edge, 0) - - for _, neighs := range g.neighbors { - for _, we := range neighs { - if _, ok := m[we]; !ok { - m[we] = struct{}{} - toReturn = append(toReturn, we.Edge) - } - } - } - - return toReturn -} - -func (g *Graph) Degree(n graph.Node) int { - if _, ok := g.nodeMap[n.ID()]; !ok { - return 0 - } - - return len(g.neighbors[n.ID()]) -} diff --git a/vendor/github.com/gonum/graph/concrete/undirected_test.go b/vendor/github.com/gonum/graph/concrete/undirected_test.go deleted file mode 100644 index cbd84cf0613e..000000000000 --- a/vendor/github.com/gonum/graph/concrete/undirected_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright ©2014 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 concrete_test - -import ( - "testing" - - "github.com/gonum/graph" - "github.com/gonum/graph/concrete" -) - -var _ graph.Graph = (*concrete.Graph)(nil) -var _ graph.Graph = (*concrete.Graph)(nil) - -func TestAssertMutableNotDirected(t *testing.T) { - var g graph.MutableUndirected = concrete.NewGraph() - if _, ok := g.(graph.Directed); ok { - t.Fatal("concrete.Graph is directed, but a MutableGraph cannot safely be directed!") - } -} - -func TestMaxID(t *testing.T) { - g := concrete.NewGraph() - nodes := make(map[graph.Node]struct{}) - for i := concrete.Node(0); i < 3; i++ { - g.AddNode(i) - nodes[i] = struct{}{} - } - g.RemoveNode(concrete.Node(0)) - delete(nodes, concrete.Node(0)) - g.RemoveNode(concrete.Node(2)) - delete(nodes, concrete.Node(2)) - n := concrete.Node(g.NewNodeID()) - g.AddNode(n) - if !g.Has(n) { - t.Error("added node does not exist in graph") - } - if _, exists := nodes[n]; exists { - t.Errorf("Created already existing node id: %v", n.ID()) - } -} diff --git a/vendor/github.com/gonum/graph/concrete/util.go b/vendor/github.com/gonum/graph/concrete/util.go deleted file mode 100644 index ee049e38fa1a..000000000000 --- a/vendor/github.com/gonum/graph/concrete/util.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright ©2014 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 concrete - -import ( - "math" - - "github.com/gonum/graph" -) - -type nodeSorter []graph.Node - -func (ns nodeSorter) Less(i, j int) bool { - return ns[i].ID() < ns[j].ID() -} - -func (ns nodeSorter) Swap(i, j int) { - ns[i], ns[j] = ns[j], ns[i] -} - -func (ns nodeSorter) Len() int { - return len(ns) -} - -// The math package only provides explicitly sized max -// values. This ensures we get the max for the actual -// type int. -const maxInt int = int(^uint(0) >> 1) - -var inf = math.Inf(1) - -func isSame(a, b float64) bool { - return a == b || (math.IsNaN(a) && math.IsNaN(b)) -} - -func max(a, b int) int { - if a > b { - return a - } else { - return b - } -} diff --git a/vendor/github.com/gonum/graph/encoding/dot/decode.go b/vendor/github.com/gonum/graph/encoding/dot/decode.go new file mode 100644 index 000000000000..22065ddc6361 --- /dev/null +++ b/vendor/github.com/gonum/graph/encoding/dot/decode.go @@ -0,0 +1,237 @@ +// Copyright ©2017 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 dot + +import ( + "fmt" + + "github.com/gonum/graph" + "github.com/gonum/graph/formats/dot" + "github.com/gonum/graph/formats/dot/ast" + "golang.org/x/tools/container/intsets" +) + +// Builder is a graph that can have user-defined nodes and edges added. +type Builder interface { + graph.Graph + graph.Builder + // NewNode adds a new node with a unique node ID to the graph. + NewNode() graph.Node + // NewEdge adds a new edge from the source to the destination node to the + // graph, or returns the existing edge if already present. + NewEdge(from, to graph.Node) graph.Edge +} + +// UnmarshalerAttr is the interface implemented by objects that can unmarshal a +// DOT attribute description of themselves. +type UnmarshalerAttr interface { + // UnmarshalDOTAttr decodes a single DOT attribute. + UnmarshalDOTAttr(attr Attribute) error +} + +// Unmarshal parses the Graphviz DOT-encoded data and stores the result in dst. +func Unmarshal(data []byte, dst Builder) error { + file, err := dot.ParseBytes(data) + if err != nil { + return err + } + if len(file.Graphs) != 1 { + return fmt.Errorf("invalid number of graphs; expected 1, got %d", len(file.Graphs)) + } + return copyGraph(dst, file.Graphs[0]) +} + +// copyGraph copies the nodes and edges from the Graphviz AST source graph to +// the destination graph. Edge direction is maintained if present. +func copyGraph(dst Builder, src *ast.Graph) (err error) { + defer func() { + switch e := recover().(type) { + case nil: + case error: + err = e + default: + panic(e) + } + }() + gen := &generator{ + directed: src.Directed, + ids: make(map[string]graph.Node), + } + for _, stmt := range src.Stmts { + gen.addStmt(dst, stmt) + } + return err +} + +// A generator keeps track of the information required for generating a gonum +// graph from a dot AST graph. +type generator struct { + // Directed graph. + directed bool + // Map from dot AST node ID to gonum node. + ids map[string]graph.Node + // Nodes processed within the context of a subgraph, that is to be used as a + // vertex of an edge. + subNodes []graph.Node + // Stack of start indices into the subgraph node slice. The top element + // corresponds to the start index of the active (or inner-most) subgraph. + subStart []int +} + +// node returns the gonum node corresponding to the given dot AST node ID, +// generating a new such node if none exist. +func (gen *generator) node(dst Builder, id string) graph.Node { + if n, ok := gen.ids[id]; ok { + return n + } + n := dst.NewNode() + gen.ids[id] = n + // Check if within the context of a subgraph, that is to be used as a vertex + // of an edge. + if gen.isInSubgraph() { + // Append node processed within the context of a subgraph, that is to be + // used as a vertex of an edge + gen.appendSubgraphNode(n) + } + return n +} + +// addStmt adds the given statement to the graph. +func (gen *generator) addStmt(dst Builder, stmt ast.Stmt) { + switch stmt := stmt.(type) { + case *ast.NodeStmt: + n := gen.node(dst, stmt.Node.ID) + if n, ok := n.(UnmarshalerAttr); ok { + for _, attr := range stmt.Attrs { + a := Attribute{ + Key: attr.Key, + Value: attr.Val, + } + if err := n.UnmarshalDOTAttr(a); err != nil { + panic(fmt.Errorf("unable to unmarshal node DOT attribute (%s=%s)", a.Key, a.Value)) + } + } + } + case *ast.EdgeStmt: + gen.addEdgeStmt(dst, stmt) + case *ast.AttrStmt: + // ignore. + case *ast.Attr: + // ignore. + case *ast.Subgraph: + for _, stmt := range stmt.Stmts { + gen.addStmt(dst, stmt) + } + default: + panic(fmt.Sprintf("unknown statement type %T", stmt)) + } +} + +// addEdgeStmt adds the given edge statement to the graph. +func (gen *generator) addEdgeStmt(dst Builder, e *ast.EdgeStmt) { + fs := gen.addVertex(dst, e.From) + ts := gen.addEdge(dst, e.To) + for _, f := range fs { + for _, t := range ts { + edge := dst.NewEdge(f, t) + if edge, ok := edge.(UnmarshalerAttr); ok { + for _, attr := range e.Attrs { + a := Attribute{ + Key: attr.Key, + Value: attr.Val, + } + if err := edge.UnmarshalDOTAttr(a); err != nil { + panic(fmt.Errorf("unable to unmarshal edge DOT attribute (%s=%s)", a.Key, a.Value)) + } + } + } + } + } +} + +// addVertex adds the given vertex to the graph, and returns its set of nodes. +func (gen *generator) addVertex(dst Builder, v ast.Vertex) []graph.Node { + switch v := v.(type) { + case *ast.Node: + n := gen.node(dst, v.ID) + return []graph.Node{n} + case *ast.Subgraph: + gen.pushSubgraph() + for _, stmt := range v.Stmts { + gen.addStmt(dst, stmt) + } + return gen.popSubgraph() + default: + panic(fmt.Sprintf("unknown vertex type %T", v)) + } +} + +// addEdge adds the given edge to the graph, and returns its set of nodes. +func (gen *generator) addEdge(dst Builder, to *ast.Edge) []graph.Node { + if !gen.directed && to.Directed { + panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex)) + } + fs := gen.addVertex(dst, to.Vertex) + if to.To != nil { + ts := gen.addEdge(dst, to.To) + for _, f := range fs { + for _, t := range ts { + dst.NewEdge(f, t) + } + } + } + return fs +} + +// pushSubgraph pushes the node start index of the active subgraph onto the +// stack. +func (gen *generator) pushSubgraph() { + gen.subStart = append(gen.subStart, len(gen.subNodes)) +} + +// popSubgraph pops the node start index of the active subgraph from the stack, +// and returns the nodes processed since. +func (gen *generator) popSubgraph() []graph.Node { + // Get nodes processed since the subgraph became active. + start := gen.subStart[len(gen.subStart)-1] + // TODO: Figure out a better way to store subgraph nodes, so that duplicates + // may not occur. + nodes := unique(gen.subNodes[start:]) + // Remove subgraph from stack. + gen.subStart = gen.subStart[:len(gen.subStart)-1] + if len(gen.subStart) == 0 { + // Remove subgraph nodes when the bottom-most subgraph has been processed. + gen.subNodes = gen.subNodes[:0] + } + return nodes +} + +// unique returns the set of unique nodes contained within ns. +func unique(ns []graph.Node) []graph.Node { + var nodes []graph.Node + var set intsets.Sparse + for _, n := range ns { + id := n.ID() + if set.Has(id) { + // skip duplicate node + continue + } + set.Insert(id) + nodes = append(nodes, n) + } + return nodes +} + +// isInSubgraph reports whether the active context is within a subgraph, that is +// to be used as a vertex of an edge. +func (gen *generator) isInSubgraph() bool { + return len(gen.subStart) > 0 +} + +// appendSubgraphNode appends the given node to the slice of nodes processed +// within the context of a subgraph. +func (gen *generator) appendSubgraphNode(n graph.Node) { + gen.subNodes = append(gen.subNodes, n) +} diff --git a/vendor/github.com/gonum/graph/encoding/dot/decode_test.go b/vendor/github.com/gonum/graph/encoding/dot/decode_test.go new file mode 100644 index 000000000000..3be74f529ce8 --- /dev/null +++ b/vendor/github.com/gonum/graph/encoding/dot/decode_test.go @@ -0,0 +1,196 @@ +// Copyright ©2017 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 dot + +import ( + "fmt" + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" +) + +func TestRoundTrip(t *testing.T) { + golden := []struct { + want string + directed bool + }{ + { + want: directed, + directed: true, + }, + { + want: undirected, + directed: false, + }, + } + for i, g := range golden { + var dst Builder + if g.directed { + dst = newDotDirectedGraph() + } else { + dst = newDotUndirectedGraph() + } + data := []byte(g.want) + if err := Unmarshal(data, dst); err != nil { + t.Errorf("i=%d: unable to unmarshal DOT graph; %v", i, err) + continue + } + buf, err := Marshal(dst, "", "", "\t", false) + if err != nil { + t.Errorf("i=%d: unable to marshal graph; %v", i, dst) + continue + } + got := string(buf) + if got != g.want { + t.Errorf("i=%d: graph content mismatch; expected `%s`, got `%s`", i, g.want, got) + continue + } + } +} + +const directed = `digraph { + // Node definitions. + 0 [label="foo 2"]; + 1 [label="bar 2"]; + + // Edge definitions. + 0 -> 1 [label="baz 2"]; +}` + +const undirected = `graph { + // Node definitions. + 0 [label="foo 2"]; + 1 [label="bar 2"]; + + // Edge definitions. + 0 -- 1 [label="baz 2"]; +}` + +// Below follows a minimal implementation of a graph capable of validating the +// round-trip encoding and decoding of DOT graphs with nodes and edges +// containing DOT attributes. + +// dotDirectedGraph extends simple.DirectedGraph to add NewNode and NewEdge +// methods for creating user-defined nodes and edges. +// +// dotDirectedGraph implements the dot.Builder interface. +type dotDirectedGraph struct { + *simple.DirectedGraph +} + +// newDotDirectedGraph returns a new directed capable of creating user-defined +// nodes and edges. +func newDotDirectedGraph() *dotDirectedGraph { + return &dotDirectedGraph{DirectedGraph: simple.NewDirectedGraph(0, 0)} +} + +// NewNode adds a new node with a unique node ID to the graph. +func (g *dotDirectedGraph) NewNode() graph.Node { + n := &dotNode{Node: simple.Node(g.NewNodeID())} + g.AddNode(n) + return n +} + +// NewEdge adds a new edge from the source to the destination node to the graph, +// or returns the existing edge if already present. +func (g *dotDirectedGraph) NewEdge(from, to graph.Node) graph.Edge { + if e := g.Edge(from, to); e != nil { + return e + } + e := &dotEdge{Edge: simple.Edge{F: from, T: to}} + g.SetEdge(e) + return e +} + +// dotUndirectedGraph extends simple.UndirectedGraph to add NewNode and NewEdge +// methods for creating user-defined nodes and edges. +// +// dotUndirectedGraph implements the dot.Builder interface. +type dotUndirectedGraph struct { + *simple.UndirectedGraph +} + +// newDotUndirectedGraph returns a new undirected capable of creating user- +// defined nodes and edges. +func newDotUndirectedGraph() *dotUndirectedGraph { + return &dotUndirectedGraph{UndirectedGraph: simple.NewUndirectedGraph(0, 0)} +} + +// NewNode adds a new node with a unique node ID to the graph. +func (g *dotUndirectedGraph) NewNode() graph.Node { + n := &dotNode{Node: simple.Node(g.NewNodeID())} + g.AddNode(n) + return n +} + +// NewEdge adds a new edge from the source to the destination node to the graph, +// or returns the existing edge if already present. +func (g *dotUndirectedGraph) NewEdge(from, to graph.Node) graph.Edge { + if e := g.Edge(from, to); e != nil { + return e + } + e := &dotEdge{Edge: simple.Edge{F: from, T: to}} + g.SetEdge(e) + return e +} + +// dotNode extends simple.Node with a label field to test round-trip encoding +// and decoding of node DOT label attributes. +type dotNode struct { + simple.Node + // Node label. + Label string +} + +// UnmarshalDOTAttr decodes a single DOT attribute. +func (n *dotNode) UnmarshalDOTAttr(attr Attribute) error { + if attr.Key != "label" { + return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key) + } + n.Label = attr.Value + return nil +} + +// DOTAttributes returns the DOT attributes of the node. +func (n *dotNode) DOTAttributes() []Attribute { + if len(n.Label) == 0 { + return nil + } + attr := Attribute{ + Key: "label", + Value: n.Label, + } + return []Attribute{attr} +} + +// dotEdge extends simple.Edge with a label field to test round-trip encoding and +// decoding of edge DOT label attributes. +type dotEdge struct { + simple.Edge + // Edge label. + Label string +} + +// UnmarshalDOTAttr decodes a single DOT attribute. +func (e *dotEdge) UnmarshalDOTAttr(attr Attribute) error { + if attr.Key != "label" { + return fmt.Errorf("unable to unmarshal node DOT attribute with key %q", attr.Key) + } + e.Label = attr.Value + return nil +} + +// DOTAttributes returns the DOT attributes of the edge. +func (e *dotEdge) DOTAttributes() []Attribute { + if len(e.Label) == 0 { + return nil + } + attr := Attribute{ + Key: "label", + Value: e.Label, + } + return []Attribute{attr} +} diff --git a/vendor/github.com/gonum/graph/encoding/dot/dot.go b/vendor/github.com/gonum/graph/encoding/dot/dot.go index 4b43839e019f..7e3dc30c55a0 100644 --- a/vendor/github.com/gonum/graph/encoding/dot/dot.go +++ b/vendor/github.com/gonum/graph/encoding/dot/dot.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/gonum/graph" + "github.com/gonum/graph/internal/ordered" ) // Node is a DOT graph node. @@ -123,7 +124,7 @@ type edge struct { func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool) error { nodes := g.Nodes() - sort.Sort(byID(nodes)) + sort.Sort(ordered.ByID(nodes)) p.buf.WriteString(p.prefix) if needsIndent { @@ -201,7 +202,7 @@ func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool havePrintedEdgeHeader := false for _, n := range nodes { to := g.From(n) - sort.Sort(byID(to)) + sort.Sort(ordered.ByID(to)) for _, t := range to { if isDirected { if p.visited[edge{inGraph: name, from: n.ID(), to: t.ID()}] { @@ -218,7 +219,7 @@ func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool if !havePrintedEdgeHeader { p.buf.WriteByte('\n') - p.buf.WriteString(strings.TrimRight(p.prefix, " \t\xa0")) // Trim whitespace suffix. + p.buf.WriteString(strings.TrimRight(p.prefix, " \t\n")) // Trim whitespace suffix. p.newline() p.buf.WriteString("// Edge definitions.") havePrintedEdgeHeader = true @@ -375,9 +376,3 @@ func (p *printer) closeBlock(b string) { p.newline() p.buf.WriteString(b) } - -type byID []graph.Node - -func (n byID) Len() int { return len(n) } -func (n byID) Less(i, j int) bool { return n[i].ID() < n[j].ID() } -func (n byID) Swap(i, j int) { n[i], n[j] = n[j], n[i] } diff --git a/vendor/github.com/gonum/graph/encoding/dot/dot_test.go b/vendor/github.com/gonum/graph/encoding/dot/dot_test.go index 31f4a71c45b9..17d3c9d8311a 100644 --- a/vendor/github.com/gonum/graph/encoding/dot/dot_test.go +++ b/vendor/github.com/gonum/graph/encoding/dot/dot_test.go @@ -5,10 +5,11 @@ package dot import ( + "math" "testing" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" ) // set is an integer set. @@ -53,20 +54,20 @@ var ( ) func directedGraphFrom(g []set) graph.Directed { - dg := concrete.NewDirectedGraph() + dg := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range g { for v := range e { - dg.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + dg.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } return dg } func undirectedGraphFrom(g []set) graph.Graph { - dg := concrete.NewGraph() + dg := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range g { for v := range e { - dg.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + dg.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } return dg @@ -83,24 +84,24 @@ func (n namedNode) ID() int { return n.id } func (n namedNode) DOTID() string { return n.name } func directedNamedIDGraphFrom(g []set) graph.Directed { - dg := concrete.NewDirectedGraph() + dg := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range g { nu := namedNode{id: u, name: alpha[u : u+1]} for v := range e { nv := namedNode{id: v, name: alpha[v : v+1]} - dg.SetEdge(concrete.Edge{F: nu, T: nv}, 0) + dg.SetEdge(simple.Edge{F: nu, T: nv}) } } return dg } func undirectedNamedIDGraphFrom(g []set) graph.Graph { - dg := concrete.NewGraph() + dg := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range g { nu := namedNode{id: u, name: alpha[u : u+1]} for v := range e { nv := namedNode{id: v, name: alpha[v : v+1]} - dg.SetEdge(concrete.Edge{F: nu, T: nv}, 0) + dg.SetEdge(simple.Edge{F: nu, T: nv}) } } return dg @@ -116,7 +117,7 @@ func (n attrNode) ID() int { return n.id } func (n attrNode) DOTAttributes() []Attribute { return n.attr } func directedNodeAttrGraphFrom(g []set, attr [][]Attribute) graph.Directed { - dg := concrete.NewDirectedGraph() + dg := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range g { var at []Attribute if u < len(attr) { @@ -128,14 +129,14 @@ func directedNodeAttrGraphFrom(g []set, attr [][]Attribute) graph.Directed { at = attr[v] } nv := attrNode{id: v, attr: at} - dg.SetEdge(concrete.Edge{F: nu, T: nv}, 0) + dg.SetEdge(simple.Edge{F: nu, T: nv}) } } return dg } func undirectedNodeAttrGraphFrom(g []set, attr [][]Attribute) graph.Graph { - dg := concrete.NewGraph() + dg := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range g { var at []Attribute if u < len(attr) { @@ -147,7 +148,7 @@ func undirectedNodeAttrGraphFrom(g []set, attr [][]Attribute) graph.Graph { at = attr[v] } nv := attrNode{id: v, attr: at} - dg.SetEdge(concrete.Edge{F: nu, T: nv}, 0) + dg.SetEdge(simple.Edge{F: nu, T: nv}) } } return dg @@ -164,7 +165,7 @@ func (n namedAttrNode) DOTID() string { return n.name } func (n namedAttrNode) DOTAttributes() []Attribute { return n.attr } func directedNamedIDNodeAttrGraphFrom(g []set, attr [][]Attribute) graph.Directed { - dg := concrete.NewDirectedGraph() + dg := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range g { var at []Attribute if u < len(attr) { @@ -176,14 +177,14 @@ func directedNamedIDNodeAttrGraphFrom(g []set, attr [][]Attribute) graph.Directe at = attr[v] } nv := namedAttrNode{id: v, name: alpha[v : v+1], attr: at} - dg.SetEdge(concrete.Edge{F: nu, T: nv}, 0) + dg.SetEdge(simple.Edge{F: nu, T: nv}) } } return dg } func undirectedNamedIDNodeAttrGraphFrom(g []set, attr [][]Attribute) graph.Graph { - dg := concrete.NewGraph() + dg := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range g { var at []Attribute if u < len(attr) { @@ -195,7 +196,7 @@ func undirectedNamedIDNodeAttrGraphFrom(g []set, attr [][]Attribute) graph.Graph at = attr[v] } nv := namedAttrNode{id: v, name: alpha[v : v+1], attr: at} - dg.SetEdge(concrete.Edge{F: nu, T: nv}, 0) + dg.SetEdge(simple.Edge{F: nu, T: nv}) } } return dg @@ -209,23 +210,24 @@ type attrEdge struct { func (e attrEdge) From() graph.Node { return e.from } func (e attrEdge) To() graph.Node { return e.to } +func (e attrEdge) Weight() float64 { return 0 } func (e attrEdge) DOTAttributes() []Attribute { return e.attr } func directedEdgeAttrGraphFrom(g []set, attr map[edge][]Attribute) graph.Directed { - dg := concrete.NewDirectedGraph() + dg := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range g { for v := range e { - dg.SetEdge(attrEdge{from: concrete.Node(u), to: concrete.Node(v), attr: attr[edge{from: u, to: v}]}, 0) + dg.SetEdge(attrEdge{from: simple.Node(u), to: simple.Node(v), attr: attr[edge{from: u, to: v}]}) } } return dg } func undirectedEdgeAttrGraphFrom(g []set, attr map[edge][]Attribute) graph.Graph { - dg := concrete.NewGraph() + dg := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range g { for v := range e { - dg.SetEdge(attrEdge{from: concrete.Node(u), to: concrete.Node(v), attr: attr[edge{from: u, to: v}]}, 0) + dg.SetEdge(attrEdge{from: simple.Node(u), to: simple.Node(v), attr: attr[edge{from: u, to: v}]}) } } return dg @@ -244,6 +246,7 @@ type portedEdge struct { func (e portedEdge) From() graph.Node { return e.from } func (e portedEdge) To() graph.Node { return e.to } +func (e portedEdge) Weight() float64 { return 0 } // TODO(kortschak): Figure out a better way to handle the fact that // headedness is an undefined concept in undirected graphs. We sort @@ -261,7 +264,7 @@ func (e portedEdge) ToPort() (port, compass string) { } func directedPortedAttrGraphFrom(g []set, attr [][]Attribute, ports map[edge]portedEdge) graph.Directed { - dg := concrete.NewDirectedGraph() + dg := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range g { var at []Attribute if u < len(attr) { @@ -275,14 +278,14 @@ func directedPortedAttrGraphFrom(g []set, attr [][]Attribute, ports map[edge]por pe := ports[edge{from: u, to: v}] pe.from = nu pe.to = attrNode{id: v, attr: at} - dg.SetEdge(pe, 0) + dg.SetEdge(pe) } } return dg } func undirectedPortedAttrGraphFrom(g []set, attr [][]Attribute, ports map[edge]portedEdge) graph.Graph { - dg := concrete.NewGraph() + dg := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range g { var at []Attribute if u < len(attr) { @@ -296,7 +299,7 @@ func undirectedPortedAttrGraphFrom(g []set, attr [][]Attribute, ports map[edge]p pe := ports[edge{from: u, to: v}] pe.from = nu pe.to = attrNode{id: v, attr: at} - dg.SetEdge(pe, 0) + dg.SetEdge(pe) } } return dg @@ -318,26 +321,26 @@ func (g graphAttributer) DOTAttributers() (graph, node, edge Attributer) { } type structuredGraph struct { - *concrete.Graph + *simple.UndirectedGraph sub []Graph } func undirectedStructuredGraphFrom(c []edge, g ...[]set) graph.Graph { - s := &structuredGraph{Graph: concrete.NewGraph()} + s := &structuredGraph{UndirectedGraph: simple.NewUndirectedGraph(0, math.Inf(1))} var base int for i, sg := range g { - sub := concrete.NewGraph() + sub := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range sg { for v := range e { - ce := concrete.Edge{F: concrete.Node(u + base), T: concrete.Node(v + base)} - sub.SetEdge(ce, 0) + ce := simple.Edge{F: simple.Node(u + base), T: simple.Node(v + base)} + sub.SetEdge(ce) } } s.sub = append(s.sub, namedGraph{id: i, Graph: sub}) base += len(sg) } for _, e := range c { - s.SetEdge(concrete.Edge{F: concrete.Node(e.from), T: concrete.Node(e.to)}, 0) + s.SetEdge(simple.Edge{F: simple.Node(e.from), T: simple.Node(e.to)}) } return s } @@ -367,25 +370,25 @@ func undirectedSubGraphFrom(g []set, s map[int][]set) graph.Graph { var base int subs := make(map[int]subGraph) for i, sg := range s { - sub := concrete.NewGraph() + sub := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range sg { for v := range e { - ce := concrete.Edge{F: concrete.Node(u + base), T: concrete.Node(v + base)} - sub.SetEdge(ce, 0) + ce := simple.Edge{F: simple.Node(u + base), T: simple.Node(v + base)} + sub.SetEdge(ce) } } subs[i] = subGraph{id: i, Graph: sub} base += len(sg) } - dg := concrete.NewGraph() + dg := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range g { var nu graph.Node if sg, ok := subs[u]; ok { sg.id += base nu = sg } else { - nu = concrete.Node(u + base) + nu = simple.Node(u + base) } for v := range e { var nv graph.Node @@ -393,9 +396,9 @@ func undirectedSubGraphFrom(g []set, s map[int][]set) graph.Graph { sg.id += base nv = sg } else { - nv = concrete.Node(v + base) + nv = simple.Node(v + base) } - dg.SetEdge(concrete.Edge{F: nu, T: nv}, 0) + dg.SetEdge(simple.Edge{F: nu, T: nv}) } } return dg diff --git a/vendor/github.com/gonum/graph/ex/fdpclust/gn.go b/vendor/github.com/gonum/graph/ex/fdpclust/gn.go index 1349094cd9a9..986fa9d85387 100644 --- a/vendor/github.com/gonum/graph/ex/fdpclust/gn.go +++ b/vendor/github.com/gonum/graph/ex/fdpclust/gn.go @@ -2,7 +2,7 @@ package main import ( "github.com/gonum/graph" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" ) type GraphNode struct { @@ -187,7 +187,7 @@ func (g *GraphNode) findNeighbors(n graph.Node, visited map[int]struct{}) []grap return nil } -func (g *GraphNode) HasEdge(u, v graph.Node) bool { +func (g *GraphNode) HasEdgeBetween(u, v graph.Node) bool { return g.EdgeBetween(u, v) != nil } @@ -199,7 +199,7 @@ func (g *GraphNode) EdgeBetween(u, v graph.Node) graph.Edge { if u.ID() == g.id || v.ID() == g.id { for _, neigh := range g.neighbors { if neigh.ID() == u.ID() || neigh.ID() == v.ID() { - return concrete.Edge{g, neigh} + return simple.Edge{F: g, T: neigh} } } return nil @@ -229,7 +229,7 @@ func (g *GraphNode) edgeBetween(u, v graph.Node, visited map[int]struct{}) graph if u.ID() == g.id || v.ID() == g.id { for _, neigh := range g.neighbors { if neigh.ID() == u.ID() || neigh.ID() == v.ID() { - return concrete.Edge{g, neigh} + return simple.Edge{F: g, T: neigh} } } return nil diff --git a/vendor/github.com/gonum/graph/formats/dot/README.md b/vendor/github.com/gonum/graph/formats/dot/README.md new file mode 100644 index 000000000000..6949ce03a529 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/README.md @@ -0,0 +1,9 @@ +# formats/dot + +## License + +The source code and any original content of the formats/dot directory is released under [Public Domain Dedication](https://creativecommons.org/publicdomain/zero/1.0/). + +The source code is also licensed under the gonum license, and users are free to choice the license which suits their needs. + +Please see github.com/gonum/license for general license information, contributors, authors, etc on the Gonum suite of packages. diff --git a/vendor/github.com/gonum/graph/formats/dot/ast/ast.go b/vendor/github.com/gonum/graph/formats/dot/ast/ast.go new file mode 100644 index 000000000000..a8ac7bf83035 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/ast/ast.go @@ -0,0 +1,408 @@ +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +// Package ast declares the types used to represent abstract syntax trees of +// Graphviz DOT graphs. +package ast + +import ( + "bytes" + "fmt" +) + +// === [ File ] ================================================================ + +// A File represents a DOT file. +// +// Examples. +// +// digraph G { +// A -> B +// } +// graph H { +// C - D +// } +type File struct { + // Graphs. + Graphs []*Graph +} + +// String returns the string representation of the file. +func (f *File) String() string { + buf := new(bytes.Buffer) + for i, graph := range f.Graphs { + if i != 0 { + buf.WriteString("\n") + } + buf.WriteString(graph.String()) + } + return buf.String() +} + +// === [ Graphs ] ============================================================== + +// A Graph represents a directed or an undirected graph. +// +// Examples. +// +// digraph G { +// A -> {B C} +// B -> C +// } +type Graph struct { + // Strict graph; multi-edges forbidden. + Strict bool + // Directed graph. + Directed bool + // Graph ID; or empty if anonymous. + ID string + // Graph statements. + Stmts []Stmt +} + +// String returns the string representation of the graph. +func (g *Graph) String() string { + buf := new(bytes.Buffer) + if g.Strict { + buf.WriteString("strict ") + } + if g.Directed { + buf.WriteString("digraph ") + } else { + buf.WriteString("graph ") + } + if len(g.ID) > 0 { + fmt.Fprintf(buf, "%s ", g.ID) + } + buf.WriteString("{\n") + for _, stmt := range g.Stmts { + fmt.Fprintf(buf, "\t%s\n", stmt) + } + buf.WriteString("}") + return buf.String() +} + +// === [ Statements ] ========================================================== + +// A Stmt represents a statement, and has one of the following underlying types. +// +// *NodeStmt +// *EdgeStmt +// *AttrStmt +// *Attr +// *Subgraph +type Stmt interface { + fmt.Stringer + // isStmt ensures that only statements can be assigned to the Stmt interface. + isStmt() +} + +// --- [ Node statement ] ------------------------------------------------------ + +// A NodeStmt represents a node statement. +// +// Examples. +// +// A [color=blue] +type NodeStmt struct { + // Node. + Node *Node + // Node attributes. + Attrs []*Attr +} + +// String returns the string representation of the node statement. +func (e *NodeStmt) String() string { + buf := new(bytes.Buffer) + buf.WriteString(e.Node.String()) + if len(e.Attrs) > 0 { + buf.WriteString(" [") + for i, attr := range e.Attrs { + if i != 0 { + buf.WriteString(" ") + } + buf.WriteString(attr.String()) + } + buf.WriteString("]") + } + return buf.String() +} + +// --- [ Edge statement ] ------------------------------------------------------ + +// An EdgeStmt represents an edge statement. +// +// Examples. +// +// A -> B +// A -> {B C} +// A -> B -> C +type EdgeStmt struct { + // Source vertex. + From Vertex + // Outgoing edge. + To *Edge + // Edge attributes. + Attrs []*Attr +} + +// String returns the string representation of the edge statement. +func (e *EdgeStmt) String() string { + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "%s %s", e.From, e.To) + if len(e.Attrs) > 0 { + buf.WriteString(" [") + for i, attr := range e.Attrs { + if i != 0 { + buf.WriteString(" ") + } + buf.WriteString(attr.String()) + } + buf.WriteString("]") + } + return buf.String() +} + +// An Edge represents an edge between two vertices. +type Edge struct { + // Directed edge. + Directed bool + // Destination vertex. + Vertex Vertex + // Outgoing edge; or nil if none. + To *Edge +} + +// String returns the string representation of the edge. +func (e *Edge) String() string { + op := "--" + if e.Directed { + op = "->" + } + if e.To != nil { + return fmt.Sprintf("%s %s %s", op, e.Vertex, e.To) + } + return fmt.Sprintf("%s %s", op, e.Vertex) +} + +// --- [ Attribute statement ] ------------------------------------------------- + +// An AttrStmt represents an attribute statement. +// +// Examples. +// +// graph [rankdir=LR] +// node [color=blue fillcolor=red] +// edge [minlen=1] +type AttrStmt struct { + // Graph component kind to which the attributes are assigned. + Kind Kind + // Attributes. + Attrs []*Attr +} + +// String returns the string representation of the attribute statement. +func (a *AttrStmt) String() string { + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "%s [", a.Kind) + for i, attr := range a.Attrs { + if i != 0 { + buf.WriteString(" ") + } + buf.WriteString(attr.String()) + } + buf.WriteString("]") + return buf.String() +} + +// Kind specifies the set of graph components to which attribute statements may +// be assigned. +type Kind uint + +// Graph component kinds. +const ( + KindGraph Kind = iota // graph + KindNode // node + KindEdge // edge +) + +// String returns the string representation of the graph component kind. +func (k Kind) String() string { + switch k { + case KindGraph: + return "graph" + case KindNode: + return "node" + case KindEdge: + return "edge" + } + panic(fmt.Sprintf("invalid graph component kind (%d)", uint(k))) +} + +// --- [ Attribute ] ----------------------------------------------------------- + +// An Attr represents an attribute. +// +// Examples. +// +// rank=same +type Attr struct { + // Attribute key. + Key string + // Attribute value. + Val string +} + +// String returns the string representation of the attribute. +func (a *Attr) String() string { + return fmt.Sprintf("%s=%s", a.Key, a.Val) +} + +// --- [ Subgraph ] ------------------------------------------------------------ + +// A Subgraph represents a subgraph vertex. +// +// Examples. +// +// subgraph S {A B C} +type Subgraph struct { + // Subgraph ID; or empty if none. + ID string + // Subgraph statements. + Stmts []Stmt +} + +// String returns the string representation of the subgraph. +func (s *Subgraph) String() string { + buf := new(bytes.Buffer) + if len(s.ID) > 0 { + fmt.Fprintf(buf, "subgraph %s ", s.ID) + } + buf.WriteString("{") + for i, stmt := range s.Stmts { + if i != 0 { + buf.WriteString(" ") + } + buf.WriteString(stmt.String()) + } + buf.WriteString("}") + return buf.String() +} + +// isStmt ensures that only statements can be assigned to the Stmt interface. +func (*NodeStmt) isStmt() {} +func (*EdgeStmt) isStmt() {} +func (*AttrStmt) isStmt() {} +func (*Attr) isStmt() {} +func (*Subgraph) isStmt() {} + +// === [ Vertices ] ============================================================ + +// A Vertex represents a vertex, and has one of the following underlying types. +// +// *Node +// *Subgraph +type Vertex interface { + fmt.Stringer + // isVertex ensures that only vertices can be assigned to the Vertex + // interface. + isVertex() +} + +// --- [ Node identifier ] ----------------------------------------------------- + +// A Node represents a node vertex. +// +// Examples. +// +// A +// A:nw +type Node struct { + // Node ID. + ID string + // Node port; or nil if none. + Port *Port +} + +// String returns the string representation of the node. +func (n *Node) String() string { + if n.Port != nil { + return fmt.Sprintf("%s%s", n.ID, n.Port) + } + return n.ID +} + +// A Port specifies where on a node an edge should be aimed. +type Port struct { + // Port ID; or empty if none. + ID string + // Compass point. + CompassPoint CompassPoint +} + +// String returns the string representation of the port. +func (p *Port) String() string { + buf := new(bytes.Buffer) + if len(p.ID) > 0 { + fmt.Fprintf(buf, ":%s", p.ID) + } + if p.CompassPoint != CompassPointDefault { + fmt.Fprintf(buf, ":%s", p.CompassPoint) + } + return buf.String() +} + +// CompassPoint specifies the set of compass points. +type CompassPoint uint + +// Compass points. +const ( + CompassPointDefault CompassPoint = iota // _ + CompassPointNorth // n + CompassPointNorthEast // ne + CompassPointEast // e + CompassPointSouthEast // se + CompassPointSouth // s + CompassPointSouthWest // sw + CompassPointWest // w + CompassPointNorthWest // nw + CompassPointCenter // c +) + +// String returns the string representation of the compass point. +func (c CompassPoint) String() string { + switch c { + case CompassPointDefault: + return "_" + case CompassPointNorth: + return "n" + case CompassPointNorthEast: + return "ne" + case CompassPointEast: + return "e" + case CompassPointSouthEast: + return "se" + case CompassPointSouth: + return "s" + case CompassPointSouthWest: + return "sw" + case CompassPointWest: + return "w" + case CompassPointNorthWest: + return "nw" + case CompassPointCenter: + return "c" + } + panic(fmt.Sprintf("invalid compass point (%d)", uint(c))) +} + +// isVertex ensures that only vertices can be assigned to the Vertex interface. +func (*Node) isVertex() {} +func (*Subgraph) isVertex() {} diff --git a/vendor/github.com/gonum/graph/formats/dot/ast/ast_test.go b/vendor/github.com/gonum/graph/formats/dot/ast/ast_test.go new file mode 100644 index 000000000000..a5473330b1ea --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/ast/ast_test.go @@ -0,0 +1,101 @@ +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package ast_test + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/gonum/graph/formats/dot" + "github.com/gonum/graph/formats/dot/ast" +) + +func TestParseFile(t *testing.T) { + golden := []struct { + in string + out string + }{ + {in: "../internal/testdata/empty.dot"}, + {in: "../internal/testdata/graph.dot"}, + {in: "../internal/testdata/digraph.dot"}, + {in: "../internal/testdata/strict.dot"}, + {in: "../internal/testdata/multi.dot"}, + {in: "../internal/testdata/named_graph.dot"}, + {in: "../internal/testdata/node_stmt.dot"}, + {in: "../internal/testdata/edge_stmt.dot"}, + {in: "../internal/testdata/attr_stmt.dot"}, + {in: "../internal/testdata/attr.dot"}, + { + in: "../internal/testdata/subgraph.dot", + out: "../internal/testdata/subgraph.golden", + }, + { + in: "../internal/testdata/semi.dot", + out: "../internal/testdata/semi.golden", + }, + { + in: "../internal/testdata/empty_attr.dot", + out: "../internal/testdata/empty_attr.golden", + }, + { + in: "../internal/testdata/attr_lists.dot", + out: "../internal/testdata/attr_lists.golden", + }, + { + in: "../internal/testdata/attr_sep.dot", + out: "../internal/testdata/attr_sep.golden", + }, + {in: "../internal/testdata/subgraph_vertex.dot"}, + { + in: "../internal/testdata/port.dot", + out: "../internal/testdata/port.golden", + }, + } + for _, g := range golden { + file, err := dot.ParseFile(g.in) + if err != nil { + t.Errorf("%q: unable to parse file; %v", g.in, err) + continue + } + // If no output path is specified, the input is already golden. + out := g.in + if len(g.out) > 0 { + out = g.out + } + buf, err := ioutil.ReadFile(out) + if err != nil { + t.Errorf("%q: unable to read file; %v", g.in, err) + continue + } + got := file.String() + // Remove trailing newline. + want := string(bytes.TrimSpace(buf)) + if got != want { + t.Errorf("%q: graph mismatch; expected %q, got %q", g.in, want, got) + } + } +} + +// Verify that all statements implement the Stmt interface. +var ( + _ ast.Stmt = (*ast.NodeStmt)(nil) + _ ast.Stmt = (*ast.EdgeStmt)(nil) + _ ast.Stmt = (*ast.AttrStmt)(nil) + _ ast.Stmt = (*ast.Attr)(nil) + _ ast.Stmt = (*ast.Subgraph)(nil) +) + +// Verify that all vertices implement the Vertex interface. +var ( + _ ast.Vertex = (*ast.Node)(nil) + _ ast.Vertex = (*ast.Subgraph)(nil) +) diff --git a/vendor/github.com/gonum/graph/formats/dot/dot.go b/vendor/github.com/gonum/graph/formats/dot/dot.go new file mode 100644 index 000000000000..890083b2593f --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/dot.go @@ -0,0 +1,63 @@ +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +// Package dot implements a parser for Graphviz DOT files. +package dot + +import ( + "fmt" + "io" + "io/ioutil" + + "github.com/gonum/graph/formats/dot/ast" + "github.com/gonum/graph/formats/dot/internal/lexer" + "github.com/gonum/graph/formats/dot/internal/parser" +) + +// ParseFile parses the given Graphviz DOT file into an AST. +func ParseFile(path string) (*ast.File, error) { + buf, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + return ParseBytes(buf) +} + +// Parse parses the given Graphviz DOT file into an AST, reading from r. +func Parse(r io.Reader) (*ast.File, error) { + buf, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + return ParseBytes(buf) +} + +// ParseBytes parses the given Graphviz DOT file into an AST, reading from b. +func ParseBytes(b []byte) (*ast.File, error) { + l := lexer.NewLexer(b) + p := parser.NewParser() + file, err := p.Parse(l) + if err != nil { + return nil, err + } + f, ok := file.(*ast.File) + if !ok { + return nil, fmt.Errorf("invalid file type; expected *ast.File, got %T", file) + } + if err := check(f); err != nil { + return nil, err + } + return f, nil +} + +// ParseString parses the given Graphviz DOT file into an AST, reading from s. +func ParseString(s string) (*ast.File, error) { + return ParseBytes([]byte(s)) +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/Makefile b/vendor/github.com/gonum/graph/formats/dot/internal/Makefile new file mode 100644 index 000000000000..e31b46124ec3 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/Makefile @@ -0,0 +1,37 @@ +gen: dot.bnf + gocc $< + # TODO: Remove once https://github.com/goccmack/gocc/issues/36 gets resolved. + ./paste_copyright.bash + find . -type f -name '*.go' | xargs goimports -w + +debug_lexer: dot.bnf + gocc -debug_lexer -v -a $< + # TODO: Remove once https://github.com/goccmack/gocc/issues/36 gets resolved. + find . -type f -name '*.go' | xargs goimports -w + +debug_parser: dot.bnf + gocc -debug_parser -v -a $< + # TODO: Remove once https://github.com/goccmack/gocc/issues/36 gets resolved. + find . -type f -name '*.go' | xargs goimports -w + +clean: + rm -f errors/errors.go + rm -f lexer/acttab.go + rm -f lexer/lexer.go + rm -f lexer/transitiontable.go + rm -f parser/action.go + rm -f parser/actiontable.go + rm -f parser/gototable.go + rm -f parser/parser.go + rm -f parser/productionstable.go + rm -f token/token.go + rm -f util/litconv.go + rm -f util/rune.go + -rmdir --ignore-fail-on-non-empty errors + -rmdir --ignore-fail-on-non-empty lexer + -rmdir --ignore-fail-on-non-empty parser + -rmdir --ignore-fail-on-non-empty token + -rmdir --ignore-fail-on-non-empty util + rm -f terminals.txt LR1_conflicts.txt LR1_sets.txt first.txt lexer_sets.txt + +.PHONY: gen debug_lexer debug_parser clean diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/astx/astx.go b/vendor/github.com/gonum/graph/formats/dot/internal/astx/astx.go new file mode 100644 index 000000000000..987124a082e8 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/astx/astx.go @@ -0,0 +1,328 @@ +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +// Package astx implements utility functions for generating abstract syntax +// trees of Graphviz DOT graphs. +package astx + +import ( + "fmt" + "strings" + + "github.com/gonum/graph/formats/dot/ast" + "github.com/gonum/graph/formats/dot/internal/token" +) + +// === [ File ] ================================================================ + +// NewFile returns a new file based on the given graph. +func NewFile(graph interface{}) (*ast.File, error) { + g, ok := graph.(*ast.Graph) + if !ok { + return nil, fmt.Errorf("invalid graph type; expected *ast.Graph, got %T", graph) + } + return &ast.File{Graphs: []*ast.Graph{g}}, nil +} + +// AppendGraph appends graph to the given file. +func AppendGraph(file, graph interface{}) (*ast.File, error) { + f, ok := file.(*ast.File) + if !ok { + return nil, fmt.Errorf("invalid file type; expected *ast.File, got %T", file) + } + g, ok := graph.(*ast.Graph) + if !ok { + return nil, fmt.Errorf("invalid graph type; expected *ast.Graph, got %T", graph) + } + f.Graphs = append(f.Graphs, g) + return f, nil +} + +// === [ Graphs ] ============================================================== + +// NewGraph returns a new graph based on the given graph strictness, direction, +// optional ID and optional statements. +func NewGraph(strict, directed, optID, optStmts interface{}) (*ast.Graph, error) { + s, ok := strict.(bool) + if !ok { + return nil, fmt.Errorf("invalid strictness type; expected bool, got %T", strict) + } + d, ok := directed.(bool) + if !ok { + return nil, fmt.Errorf("invalid direction type; expected bool, got %T", directed) + } + id, ok := optID.(string) + if optID != nil && !ok { + return nil, fmt.Errorf("invalid ID type; expected string or nil, got %T", optID) + } + stmts, ok := optStmts.([]ast.Stmt) + if optStmts != nil && !ok { + return nil, fmt.Errorf("invalid statements type; expected []ast.Stmt or nil, got %T", optStmts) + } + return &ast.Graph{Strict: s, Directed: d, ID: id, Stmts: stmts}, nil +} + +// === [ Statements ] ========================================================== + +// NewStmtList returns a new statement list based on the given statement. +func NewStmtList(stmt interface{}) ([]ast.Stmt, error) { + s, ok := stmt.(ast.Stmt) + if !ok { + return nil, fmt.Errorf("invalid statement type; expected ast.Stmt, got %T", stmt) + } + return []ast.Stmt{s}, nil +} + +// AppendStmt appends stmt to the given statement list. +func AppendStmt(list, stmt interface{}) ([]ast.Stmt, error) { + l, ok := list.([]ast.Stmt) + if !ok { + return nil, fmt.Errorf("invalid statement list type; expected []ast.Stmt, got %T", list) + } + s, ok := stmt.(ast.Stmt) + if !ok { + return nil, fmt.Errorf("invalid statement type; expected ast.Stmt, got %T", stmt) + } + return append(l, s), nil +} + +// --- [ Node statement ] ------------------------------------------------------ + +// NewNodeStmt returns a new node statement based on the given node and optional +// attributes. +func NewNodeStmt(node, optAttrs interface{}) (*ast.NodeStmt, error) { + n, ok := node.(*ast.Node) + if !ok { + return nil, fmt.Errorf("invalid node type; expected *ast.Node, got %T", node) + } + attrs, ok := optAttrs.([]*ast.Attr) + if optAttrs != nil && !ok { + return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs) + } + return &ast.NodeStmt{Node: n, Attrs: attrs}, nil +} + +// --- [ Edge statement ] ------------------------------------------------------ + +// NewEdgeStmt returns a new edge statement based on the given source vertex, +// outgoing edge and optional attributes. +func NewEdgeStmt(from, to, optAttrs interface{}) (*ast.EdgeStmt, error) { + f, ok := from.(ast.Vertex) + if !ok { + return nil, fmt.Errorf("invalid source vertex type; expected ast.Vertex, got %T", from) + } + t, ok := to.(*ast.Edge) + if !ok { + return nil, fmt.Errorf("invalid outgoing edge type; expected *ast.Edge, got %T", to) + } + attrs, ok := optAttrs.([]*ast.Attr) + if optAttrs != nil && !ok { + return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs) + } + return &ast.EdgeStmt{From: f, To: t, Attrs: attrs}, nil +} + +// NewEdge returns a new edge based on the given edge direction, destination +// vertex and optional outgoing edge. +func NewEdge(directed, vertex, optTo interface{}) (*ast.Edge, error) { + d, ok := directed.(bool) + if !ok { + return nil, fmt.Errorf("invalid direction type; expected bool, got %T", directed) + } + v, ok := vertex.(ast.Vertex) + if !ok { + return nil, fmt.Errorf("invalid destination vertex type; expected ast.Vertex, got %T", vertex) + } + to, ok := optTo.(*ast.Edge) + if optTo != nil && !ok { + return nil, fmt.Errorf("invalid outgoing edge type; expected *ast.Edge or nil, got %T", optTo) + } + return &ast.Edge{Directed: d, Vertex: v, To: to}, nil +} + +// --- [ Attribute statement ] ------------------------------------------------- + +// NewAttrStmt returns a new attribute statement based on the given graph +// component kind and attributes. +func NewAttrStmt(kind, optAttrs interface{}) (*ast.AttrStmt, error) { + k, ok := kind.(ast.Kind) + if !ok { + return nil, fmt.Errorf("invalid graph component kind type; expected ast.Kind, got %T", kind) + } + attrs, ok := optAttrs.([]*ast.Attr) + if optAttrs != nil && !ok { + return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs) + } + return &ast.AttrStmt{Kind: k, Attrs: attrs}, nil +} + +// NewAttrList returns a new attribute list based on the given attribute. +func NewAttrList(attr interface{}) ([]*ast.Attr, error) { + a, ok := attr.(*ast.Attr) + if !ok { + return nil, fmt.Errorf("invalid attribute type; expected *ast.Attr, got %T", attr) + } + return []*ast.Attr{a}, nil +} + +// AppendAttr appends attr to the given attribute list. +func AppendAttr(list, attr interface{}) ([]*ast.Attr, error) { + l, ok := list.([]*ast.Attr) + if !ok { + return nil, fmt.Errorf("invalid attribute list type; expected []*ast.Attr, got %T", list) + } + a, ok := attr.(*ast.Attr) + if !ok { + return nil, fmt.Errorf("invalid attribute type; expected *ast.Attr, got %T", attr) + } + return append(l, a), nil +} + +// AppendAttrList appends the optional attrs to the given optional attribute +// list. +func AppendAttrList(optList, optAttrs interface{}) ([]*ast.Attr, error) { + list, ok := optList.([]*ast.Attr) + if optList != nil && !ok { + return nil, fmt.Errorf("invalid attribute list type; expected []*ast.Attr or nil, got %T", optList) + } + attrs, ok := optAttrs.([]*ast.Attr) + if optAttrs != nil && !ok { + return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs) + } + return append(list, attrs...), nil +} + +// --- [ Attribute ] ----------------------------------------------------------- + +// NewAttr returns a new attribute based on the given key-value pair. +func NewAttr(key, val interface{}) (*ast.Attr, error) { + k, ok := key.(string) + if !ok { + return nil, fmt.Errorf("invalid key type; expected string, got %T", key) + } + v, ok := val.(string) + if !ok { + return nil, fmt.Errorf("invalid value type; expected string, got %T", val) + } + return &ast.Attr{Key: k, Val: v}, nil +} + +// --- [ Subgraph ] ------------------------------------------------------------ + +// NewSubgraph returns a new subgraph based on the given optional subgraph ID +// and optional statements. +func NewSubgraph(optID, optStmts interface{}) (*ast.Subgraph, error) { + id, ok := optID.(string) + if optID != nil && !ok { + return nil, fmt.Errorf("invalid ID type; expected string or nil, got %T", optID) + } + stmts, ok := optStmts.([]ast.Stmt) + if optStmts != nil && !ok { + return nil, fmt.Errorf("invalid statements type; expected []ast.Stmt or nil, got %T", optStmts) + } + return &ast.Subgraph{ID: id, Stmts: stmts}, nil +} + +// === [ Vertices ] ============================================================ + +// --- [ Node identifier ] ----------------------------------------------------- + +// NewNode returns a new node based on the given node id and optional port. +func NewNode(id, optPort interface{}) (*ast.Node, error) { + i, ok := id.(string) + if !ok { + return nil, fmt.Errorf("invalid ID type; expected string, got %T", id) + } + port, ok := optPort.(*ast.Port) + if optPort != nil && !ok { + return nil, fmt.Errorf("invalid port type; expected *ast.Port or nil, got %T", optPort) + } + return &ast.Node{ID: i, Port: port}, nil +} + +// NewPort returns a new port based on the given id and optional compass point. +func NewPort(id, optCompassPoint interface{}) (*ast.Port, error) { + // Note, if optCompassPoint is nil, id may be either an identifier or a + // compass point. + // + // The following strings are valid compass points: + // + // "n", "ne", "e", "se", "s", "sw", "w", "nw", "c" and "_" + i, ok := id.(string) + if !ok { + return nil, fmt.Errorf("invalid ID type; expected string, got %T", id) + } + + // Early return if optional compass point is absent and ID is a valid compass + // point. + if optCompassPoint == nil { + if compassPoint, ok := getCompassPoint(i); ok { + return &ast.Port{CompassPoint: compassPoint}, nil + } + } + + c, ok := optCompassPoint.(string) + if optCompassPoint != nil && !ok { + return nil, fmt.Errorf("invalid compass point type; expected string or nil, got %T", optCompassPoint) + } + compassPoint, _ := getCompassPoint(c) + return &ast.Port{ID: i, CompassPoint: compassPoint}, nil +} + +// getCompassPoint returns the corresponding compass point to the given string, +// and a boolean value indicating if such a compass point exists. +func getCompassPoint(s string) (ast.CompassPoint, bool) { + switch s { + case "_": + return ast.CompassPointDefault, true + case "n": + return ast.CompassPointNorth, true + case "ne": + return ast.CompassPointNorthEast, true + case "e": + return ast.CompassPointEast, true + case "se": + return ast.CompassPointSouthEast, true + case "s": + return ast.CompassPointSouth, true + case "sw": + return ast.CompassPointSouthWest, true + case "w": + return ast.CompassPointWest, true + case "nw": + return ast.CompassPointNorthWest, true + case "c": + return ast.CompassPointCenter, true + } + return ast.CompassPointDefault, false +} + +// === [ Identifiers ] ========================================================= + +// NewID returns a new identifier based on the given ID token. +func NewID(id interface{}) (string, error) { + i, ok := id.(*token.Token) + if !ok { + return "", fmt.Errorf("invalid identifier type; expected *token.Token, got %T", id) + } + s := string(i.Lit) + + // As another aid for readability, dot allows double-quoted strings to span + // multiple physical lines using the standard C convention of a backslash + // immediately preceding a newline character. + if strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`) { + // Strip "\\\n" sequences. + s = strings.Replace(s, "\\\n", "", -1) + } + + // TODO: Add support for concatenated using a '+' operator. + + return s, nil +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/astx/astx_test.go b/vendor/github.com/gonum/graph/formats/dot/internal/astx/astx_test.go new file mode 100644 index 000000000000..68560dbe8f75 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/astx/astx_test.go @@ -0,0 +1,90 @@ +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package astx_test + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/gonum/graph/formats/dot" +) + +func TestParseFile(t *testing.T) { + golden := []struct { + in string + out string + }{ + {in: "../testdata/empty.dot"}, + {in: "../testdata/graph.dot"}, + {in: "../testdata/digraph.dot"}, + {in: "../testdata/strict.dot"}, + {in: "../testdata/multi.dot"}, + {in: "../testdata/named_graph.dot"}, + {in: "../testdata/node_stmt.dot"}, + {in: "../testdata/edge_stmt.dot"}, + {in: "../testdata/attr_stmt.dot"}, + {in: "../testdata/attr.dot"}, + { + in: "../testdata/subgraph.dot", + out: "../testdata/subgraph.golden", + }, + { + in: "../testdata/semi.dot", + out: "../testdata/semi.golden", + }, + { + in: "../testdata/empty_attr.dot", + out: "../testdata/empty_attr.golden", + }, + { + in: "../testdata/attr_lists.dot", + out: "../testdata/attr_lists.golden", + }, + { + in: "../testdata/attr_sep.dot", + out: "../testdata/attr_sep.golden", + }, + {in: "../testdata/subgraph_vertex.dot"}, + { + in: "../testdata/port.dot", + out: "../testdata/port.golden", + }, + {in: "../testdata/quoted_id.dot"}, + { + in: "../testdata/backslash_newline_id.dot", + out: "../testdata/backslash_newline_id.golden", + }, + } + for _, g := range golden { + file, err := dot.ParseFile(g.in) + if err != nil { + t.Errorf("%q: unable to parse file; %v", g.in, err) + continue + } + // If no output path is specified, the input is already golden. + out := g.in + if len(g.out) > 0 { + out = g.out + } + buf, err := ioutil.ReadFile(out) + if err != nil { + t.Errorf("%q: unable to read file; %v", g.in, err) + continue + } + got := file.String() + // Remove trailing newline. + want := string(bytes.TrimSpace(buf)) + if got != want { + t.Errorf("%q: graph mismatch; expected `%s`, got `%s`", g.in, want, got) + } + } +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/dot.bnf b/vendor/github.com/gonum/graph/formats/dot/internal/dot.bnf new file mode 100644 index 000000000000..a86a867c3204 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/dot.bnf @@ -0,0 +1,358 @@ +// The DOT Language +// +// http://www.graphviz.org/doc/info/lang.html + +// ### [ Tokens ] ############################################################## + +// The keywords node, edge, graph, digraph, subgraph, and strict are case- +// independent. + +node + : 'n' 'o' 'd' 'e' + | 'N' 'o' 'd' 'e' + | 'N' 'O' 'D' 'E' +; + +edge + : 'e' 'd' 'g' 'e' + | 'E' 'd' 'g' 'e' + | 'E' 'D' 'G' 'E' +; + +// TODO: Rename graphx to graph once gocc#20 is fixed [1]. +// +// [1]: https://github.com/goccmack/gocc/issues/20 + +graphx + : 'g' 'r' 'a' 'p' 'h' + | 'G' 'r' 'a' 'p' 'h' + | 'G' 'R' 'A' 'P' 'H' +; + +digraph + : 'd' 'i' 'g' 'r' 'a' 'p' 'h' + | 'D' 'i' 'g' 'r' 'a' 'p' 'h' + | 'd' 'i' 'G' 'r' 'a' 'p' 'h' + | 'D' 'i' 'G' 'r' 'a' 'p' 'h' + | 'D' 'I' 'G' 'R' 'A' 'P' 'H' +; + +subgraph + : 's' 'u' 'b' 'g' 'r' 'a' 'p' 'h' + | 'S' 'u' 'b' 'g' 'r' 'a' 'p' 'h' + | 's' 'u' 'b' 'G' 'r' 'a' 'p' 'h' + | 'S' 'u' 'b' 'G' 'r' 'a' 'p' 'h' + | 'S' 'U' 'B' 'G' 'R' 'A' 'P' 'H' +; + +strict + : 's' 't' 'r' 'i' 'c' 't' + | 'S' 't' 'r' 'i' 'c' 't' + | 'S' 'T' 'R' 'I' 'C' 'T' +; + +// An arbitrary ASCII character except null (0x00), double quote (0x22) and +// backslash (0x5C). +_ascii_char + // skip null (0x00) + : '\x01' - '\x21' + // skip double quote (0x22) + | '\x23' - '\x5B' + // skip backslash (0x5C) + | '\x5D' - '\x7F' +; + +_ascii_letter + : 'a' - 'z' + | 'A' - 'Z' +; + +_ascii_digit : '0' - '9' ; + +_unicode_char + : _ascii_char + | _unicode_byte +; + +_unicode_byte + : '\u0080' - '\uFFFC' + // skip invalid code point (\uFFFD) + | '\uFFFE' - '\U0010FFFF' +; + +_letter : _ascii_letter | _unicode_byte | '_' ; +_decimal_digit : _ascii_digit ; +_decimals : _decimal_digit { _decimal_digit } ; + +// An ID is one of the following: +// +// 1) Any string of alphabetic ([a-zA-Z\200-\377]) characters, underscores +// ('_') or digits ([0-9]), not beginning with a digit; +// +// 2) a numeral [-]?(.[0-9]+ | [0-9]+(.[0-9]*)? ); +// +// 3) any double-quoted string ("...") possibly containing escaped quotes +// (\"); +// +// 4) an HTML string (<...>). + +id + : _letter { _letter | _decimal_digit } + | _int_lit + | _string_lit + | _html_lit +; + +_int_lit + : [ '-' ] '.' _decimals + | [ '-' ] _decimals [ '.' { _decimal_digit } ] +; + +// In quoted strings in DOT, the only escaped character is double-quote ("). +// That is, in quoted strings, the dyad \" is converted to "; all other +// characters are left unchanged. In particular, \\ remains \\. + +// As another aid for readability, dot allows double-quoted strings to span +// multiple physical lines using the standard C convention of a backslash +// immediately preceding a newline character. + +// In addition, double-quoted strings can be concatenated using a '+' operator. + +_escaped_char : '\\' ( _unicode_char | '"' | '\\' ) ; +_char : _unicode_char | _escaped_char ; +_string_lit : '"' { _char } '"' ; + +// An arbitrary HTML character except null (0x00), left angle bracket (0x3C) and +// right angle bracket (0x3E). +_html_char + // skip null (0x00) + : '\x01' - '\x3B' + // skip left angle bracket (0x3C) + | '\x3D' + // skip right angle bracket (0x3E) + | '\x3F' - '\xFF' +; + +_html_chars : { _html_char } ; +_html_tag : '<' _html_chars '>' ; +_html_lit : '<' { _html_chars | _html_tag } '>' ; + +// The language supports C++-style comments: /* */ and //. In addition, a line +// beginning with a '#' character is considered a line output from a C +// preprocessor (e.g., # 34 to indicate line 34 ) and discarded. + +_line_comment + : '/' '/' { . } '\n' + | '#' { . } '\n' +; + +_block_comment : '/' '*' { . | '*' } '*' '/' ; +!comment : _line_comment | _block_comment ; + +!whitespace : ' ' | '\t' | '\r' | '\n' ; + +// ### [ Syntax ] ############################################################## + +<< import ( + "github.com/gonum/graph/formats/dot/ast" + "github.com/gonum/graph/formats/dot/internal/astx" +) >> + +// === [ Files ] =============================================================== + +File + : Graph << astx.NewFile($0) >> + | File Graph << astx.AppendGraph($0, $1) >> +; + +// === [ Graphs ] ============================================================== + +// Graph : [ "strict" ] ( "graph" | "digraph" ) [ ID ] "{" [ StmtList ] "}" + +Graph + : OptStrict DirectedGraph OptID + "{" OptStmtList "}" << astx.NewGraph($0, $1, $2, $4) >> +; + +OptStrict + : empty << false, nil >> + | strict << true, nil >> +; + +DirectedGraph + : graphx << false, nil >> + | digraph << true, nil >> +; + +// === [ Statements ] ========================================================== + +// StmtList +// : Stmt [ ";" ] +// | StmtList Stmt [ ";" ] + +StmtList + : Stmt OptSemi << astx.NewStmtList($0) >> + | StmtList Stmt OptSemi << astx.AppendStmt($0, $1) >> +; + +OptStmtList + : empty + | StmtList +; + +Stmt + : NodeStmt + | EdgeStmt + | AttrStmt + | Attr + | Subgraph +; + +OptSemi + : empty + | ";" +; + +// --- [ Node statement ] ------------------------------------------------------ + +// NodeStmt : Node [ AttrList ] + +NodeStmt + : Node OptAttrList << astx.NewNodeStmt($0, $1) >> +; + +// --- [ Edge statement ] ------------------------------------------------------ + +// EdgeStmt : ( Node | Subgraph ) Edge [ AttrList ] + +EdgeStmt + : Vertex Edge OptAttrList << astx.NewEdgeStmt($0, $1, $2) >> +; + +// Edge : ( "--" | "-->" ) ( Node | Subgraph ) [ Edge ] + +Edge + : DirectedEdge Vertex OptEdge << astx.NewEdge($0, $1, $2) >> +; + +DirectedEdge + : "--" << false, nil >> + | "->" << true, nil >> +; + +OptEdge + : empty + | Edge +; + +// --- [ Attribute statement ] ------------------------------------------------- + +// AttrStmt : ( "graph" | "node" | "edge" ) AttrList + +AttrStmt + : Component AttrList << astx.NewAttrStmt($0, $1) >> +; + +Component + : graphx << ast.KindGraph, nil >> + | node << ast.KindNode, nil >> + | edge << ast.KindEdge, nil >> +; + +// AttrList : "[" [ AList ] "]" [ AttrList ] + +AttrList + : "[" OptAList "]" << $1, nil >> + | AttrList "[" OptAList "]" << astx.AppendAttrList($0, $2) >> +; + +OptAttrList + : empty + | AttrList +; + +// AList +// : Attr [ ( ";" | "," ) ] +// | AList Attr [ ( ";" | "," ) ] + +AList + : Attr OptSep << astx.NewAttrList($0) >> + | AList Attr OptSep << astx.AppendAttr($0, $1) >> +; + +OptAList + : empty + | AList +; + +OptSep + : empty + | ";" + | "," +; + +// --- [ Attribute ] ----------------------------------------------------------- + +Attr + : ID "=" ID << astx.NewAttr($0, $2) >> +; + +// --- [ Subgraph ] ------------------------------------------------------------ + +// Subgraph : [ "subgraph" [ ID ] ] "{" [ StmtList ] "}" + +Subgraph + : OptSubgraphID "{" OptStmtList "}" << astx.NewSubgraph($0, $2) >> +; + +OptSubgraphID + : empty + | subgraph OptID << $1, nil >> +; + +// === [ Vertices ] ============================================================ + +Vertex + : Node + | Subgraph +; + +// --- [ Node identifier ] ----------------------------------------------------- + +// Node : ID [ Port ] + +Node + : ID OptPort << astx.NewNode($0, $1) >> +; + +// Port +// : ":" ID [ ":" CompassPoint ] +// | ":" CompassPoint +// +// CompassPoint +// : "n" | "ne" | "e" | "se" | "s" | "sw" | "w" | "nw" | "c" | "_" + +// Note also that the allowed compass point values are not keywords, so these +// strings can be used elsewhere as ordinary identifiers and, conversely, the +// parser will actually accept any identifier. + +Port + : ":" ID << astx.NewPort($1, nil) >> + | ":" ID ":" ID << astx.NewPort($1, $3) >> +; + +OptPort + : empty + | Port +; + +// === [ Identifiers ] ========================================================= + +ID + : id << astx.NewID($0) >> +; + +OptID + : empty << "", nil >> + | ID +; diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/errors/errors.go b/vendor/github.com/gonum/graph/formats/dot/internal/errors/errors.go new file mode 100644 index 000000000000..81009b0ef415 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/errors/errors.go @@ -0,0 +1,66 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package errors + +import ( + "bytes" + "fmt" + + "github.com/gonum/graph/formats/dot/internal/token" +) + +type ErrorSymbol interface { +} + +type Error struct { + Err error + ErrorToken *token.Token + ErrorSymbols []ErrorSymbol + ExpectedTokens []string + StackTop int +} + +func (E *Error) String() string { + w := new(bytes.Buffer) + fmt.Fprintf(w, "Error") + if E.Err != nil { + fmt.Fprintf(w, " %s\n", E.Err) + } else { + fmt.Fprintf(w, "\n") + } + fmt.Fprintf(w, "Token: type=%d, lit=%s\n", E.ErrorToken.Type, E.ErrorToken.Lit) + fmt.Fprintf(w, "Pos: offset=%d, line=%d, column=%d\n", E.ErrorToken.Pos.Offset, E.ErrorToken.Pos.Line, E.ErrorToken.Pos.Column) + fmt.Fprintf(w, "Expected one of: ") + for _, sym := range E.ExpectedTokens { + fmt.Fprintf(w, "%s ", sym) + } + fmt.Fprintf(w, "ErrorSymbol:\n") + for _, sym := range E.ErrorSymbols { + fmt.Fprintf(w, "%v\n", sym) + } + return w.String() +} + +func (e *Error) Error() string { + w := new(bytes.Buffer) + fmt.Fprintf(w, "Error in S%d: %s, %s", e.StackTop, token.TokMap.TokenString(e.ErrorToken), e.ErrorToken.Pos.String()) + if e.Err != nil { + fmt.Fprintf(w, e.Err.Error()) + } else { + fmt.Fprintf(w, ", expected one of: ") + for _, expected := range e.ExpectedTokens { + fmt.Fprintf(w, "%s ", expected) + } + } + return w.String() +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/lexer/acttab.go b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/acttab.go new file mode 100644 index 000000000000..4cecfb59ff18 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/acttab.go @@ -0,0 +1,597 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package lexer + +import ( + "fmt" + + "github.com/gonum/graph/formats/dot/internal/token" +) + +type ActionTable [NumStates]ActionRow + +type ActionRow struct { + Accept token.Type + Ignore string +} + +func (this ActionRow) String() string { + return fmt.Sprintf("Accept=%d, Ignore=%s", this.Accept, this.Ignore) +} + +var ActTab = ActionTable{ + ActionRow{ // S0 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S1 + Accept: -1, + Ignore: "!whitespace", + }, + ActionRow{ // S2 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S3 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S4 + Accept: 15, + Ignore: "", + }, + ActionRow{ // S5 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S6 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S7 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S8 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S9 + Accept: 18, + Ignore: "", + }, + ActionRow{ // S10 + Accept: 8, + Ignore: "", + }, + ActionRow{ // S11 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S12 + Accept: 16, + Ignore: "", + }, + ActionRow{ // S13 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S14 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S15 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S16 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S17 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S18 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S19 + Accept: 13, + Ignore: "", + }, + ActionRow{ // S20 + Accept: 14, + Ignore: "", + }, + ActionRow{ // S21 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S22 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S23 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S24 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S25 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S26 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S27 + Accept: 2, + Ignore: "", + }, + ActionRow{ // S28 + Accept: 3, + Ignore: "", + }, + ActionRow{ // S29 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S30 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S31 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S32 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S33 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S34 + Accept: -1, + Ignore: "!comment", + }, + ActionRow{ // S35 + Accept: 9, + Ignore: "", + }, + ActionRow{ // S36 + Accept: 10, + Ignore: "", + }, + ActionRow{ // S37 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S38 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S39 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S40 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S41 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S42 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S43 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S44 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S45 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S46 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S47 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S48 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S49 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S50 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S51 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S52 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S53 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S54 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S55 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S56 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S57 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S58 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S59 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S60 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S61 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S62 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S63 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S64 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S65 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S66 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S67 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S68 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S69 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S70 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S71 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S72 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S73 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S74 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S75 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S76 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S77 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S78 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S79 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S80 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S81 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S82 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S83 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S84 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S85 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S86 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S87 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S88 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S89 + Accept: -1, + Ignore: "!comment", + }, + ActionRow{ // S90 + Accept: 0, + Ignore: "", + }, + ActionRow{ // S91 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S92 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S93 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S94 + Accept: 12, + Ignore: "", + }, + ActionRow{ // S95 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S96 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S97 + Accept: 11, + Ignore: "", + }, + ActionRow{ // S98 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S99 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S100 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S101 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S102 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S103 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S104 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S105 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S106 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S107 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S108 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S109 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S110 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S111 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S112 + Accept: 6, + Ignore: "", + }, + ActionRow{ // S113 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S114 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S115 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S116 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S117 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S118 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S119 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S120 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S121 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S122 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S123 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S124 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S125 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S126 + Accept: 5, + Ignore: "", + }, + ActionRow{ // S127 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S128 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S129 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S130 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S131 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S132 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S133 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S134 + Accept: 7, + Ignore: "", + }, + ActionRow{ // S135 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S136 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S137 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S138 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S139 + Accept: 19, + Ignore: "", + }, + ActionRow{ // S140 + Accept: 17, + Ignore: "", + }, +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/lexer/lexer.go b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/lexer.go new file mode 100644 index 000000000000..be647b8bb030 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/lexer.go @@ -0,0 +1,338 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package lexer + +import ( + // "fmt" + "io/ioutil" + "unicode/utf8" + + // "github.com/gonum/graph/formats/dot/internal/util" + "github.com/gonum/graph/formats/dot/internal/token" +) + +const ( + NoState = -1 + NumStates = 141 + NumSymbols = 184 +) + +type Lexer struct { + src []byte + pos int + line int + column int +} + +func NewLexer(src []byte) *Lexer { + lexer := &Lexer{ + src: src, + pos: 0, + line: 1, + column: 1, + } + return lexer +} + +func NewLexerFile(fpath string) (*Lexer, error) { + src, err := ioutil.ReadFile(fpath) + if err != nil { + return nil, err + } + return NewLexer(src), nil +} + +func (this *Lexer) Scan() (tok *token.Token) { + + // fmt.Printf("Lexer.Scan() pos=%d\n", this.pos) + + tok = new(token.Token) + if this.pos >= len(this.src) { + tok.Type = token.EOF + tok.Pos.Offset, tok.Pos.Line, tok.Pos.Column = this.pos, this.line, this.column + return + } + start, startLine, startColumn, end := this.pos, this.line, this.column, 0 + tok.Type = token.INVALID + state, rune1, size := 0, rune(-1), 0 + for state != -1 { + + // fmt.Printf("\tpos=%d, line=%d, col=%d, state=%d\n", this.pos, this.line, this.column, state) + + if this.pos >= len(this.src) { + rune1 = -1 + } else { + rune1, size = utf8.DecodeRune(this.src[this.pos:]) + this.pos += size + } + + // Production start + if rune1 != -1 { + state = TransTab[state](rune1) + } else { + state = -1 + } + // Production end + + // Debug start + // nextState := -1 + // if rune1 != -1 { + // nextState = TransTab[state](rune1) + // } + // fmt.Printf("\tS%d, : tok=%s, rune == %s(%x), next state == %d\n", state, token.TokMap.Id(tok.Type), util.RuneToString(rune1), rune1, nextState) + // fmt.Printf("\t\tpos=%d, size=%d, start=%d, end=%d\n", this.pos, size, start, end) + // if nextState != -1 { + // fmt.Printf("\t\taction:%s\n", ActTab[nextState].String()) + // } + // state = nextState + // Debug end + + if state != -1 { + + switch rune1 { + case '\n': + this.line++ + this.column = 1 + case '\r': + this.column = 1 + case '\t': + this.column += 4 + default: + this.column++ + } + + switch { + case ActTab[state].Accept != -1: + tok.Type = ActTab[state].Accept + // fmt.Printf("\t Accept(%s), %s(%d)\n", string(act), token.TokMap.Id(tok), tok) + end = this.pos + case ActTab[state].Ignore != "": + // fmt.Printf("\t Ignore(%s)\n", string(act)) + start, startLine, startColumn = this.pos, this.line, this.column + state = 0 + if start >= len(this.src) { + tok.Type = token.EOF + } + + } + } else { + if tok.Type == token.INVALID { + end = this.pos + } + } + } + if end > start { + this.pos = end + tok.Lit = this.src[start:end] + } else { + tok.Lit = []byte{} + } + tok.Pos.Offset, tok.Pos.Line, tok.Pos.Column = start, startLine, startColumn + + // fmt.Printf("Token at %s: %s \"%s\"\n", tok.String(), token.TokMap.Id(tok.Type), tok.Lit) + + return +} + +func (this *Lexer) Reset() { + this.pos = 0 +} + +/* +Lexer symbols: +0: 'n' +1: 'o' +2: 'd' +3: 'e' +4: 'N' +5: 'o' +6: 'd' +7: 'e' +8: 'N' +9: 'O' +10: 'D' +11: 'E' +12: 'e' +13: 'd' +14: 'g' +15: 'e' +16: 'E' +17: 'd' +18: 'g' +19: 'e' +20: 'E' +21: 'D' +22: 'G' +23: 'E' +24: 'g' +25: 'r' +26: 'a' +27: 'p' +28: 'h' +29: 'G' +30: 'r' +31: 'a' +32: 'p' +33: 'h' +34: 'G' +35: 'R' +36: 'A' +37: 'P' +38: 'H' +39: 'd' +40: 'i' +41: 'g' +42: 'r' +43: 'a' +44: 'p' +45: 'h' +46: 'D' +47: 'i' +48: 'g' +49: 'r' +50: 'a' +51: 'p' +52: 'h' +53: 'd' +54: 'i' +55: 'G' +56: 'r' +57: 'a' +58: 'p' +59: 'h' +60: 'D' +61: 'i' +62: 'G' +63: 'r' +64: 'a' +65: 'p' +66: 'h' +67: 'D' +68: 'I' +69: 'G' +70: 'R' +71: 'A' +72: 'P' +73: 'H' +74: 's' +75: 'u' +76: 'b' +77: 'g' +78: 'r' +79: 'a' +80: 'p' +81: 'h' +82: 'S' +83: 'u' +84: 'b' +85: 'g' +86: 'r' +87: 'a' +88: 'p' +89: 'h' +90: 's' +91: 'u' +92: 'b' +93: 'G' +94: 'r' +95: 'a' +96: 'p' +97: 'h' +98: 'S' +99: 'u' +100: 'b' +101: 'G' +102: 'r' +103: 'a' +104: 'p' +105: 'h' +106: 'S' +107: 'U' +108: 'B' +109: 'G' +110: 'R' +111: 'A' +112: 'P' +113: 'H' +114: 's' +115: 't' +116: 'r' +117: 'i' +118: 'c' +119: 't' +120: 'S' +121: 't' +122: 'r' +123: 'i' +124: 'c' +125: 't' +126: 'S' +127: 'T' +128: 'R' +129: 'I' +130: 'C' +131: 'T' +132: '{' +133: '}' +134: ';' +135: '-' +136: '-' +137: '-' +138: '>' +139: '[' +140: ']' +141: ',' +142: '=' +143: ':' +144: '_' +145: '-' +146: '.' +147: '-' +148: '.' +149: '\' +150: '"' +151: '\' +152: '"' +153: '"' +154: '=' +155: '<' +156: '>' +157: '<' +158: '>' +159: '/' +160: '/' +161: '\n' +162: '#' +163: '\n' +164: '/' +165: '*' +166: '*' +167: '*' +168: '/' +169: ' ' +170: '\t' +171: '\r' +172: '\n' +173: \u0001-'!' +174: '#'-'[' +175: ']'-\u007f +176: 'a'-'z' +177: 'A'-'Z' +178: '0'-'9' +179: \u0080-\ufffc +180: \ufffe-\U0010ffff +181: \u0001-';' +182: '?'-\u00ff +183: . + +*/ diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/lexer/lexer_test.go b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/lexer_test.go new file mode 100644 index 000000000000..fedf5e8d65fc --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/lexer_test.go @@ -0,0 +1,54 @@ +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package lexer_test + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/gonum/graph/formats/dot" +) + +func TestParseFile(t *testing.T) { + golden := []struct { + in string + out string + }{ + { + in: "testdata/tokens.dot", + out: "testdata/tokens.golden", + }, + } + for _, g := range golden { + file, err := dot.ParseFile(g.in) + if err != nil { + t.Errorf("%q: unable to parse file; %v", g.in, err) + continue + } + // If no output path is specified, the input is already golden. + out := g.in + if len(g.out) > 0 { + out = g.out + } + buf, err := ioutil.ReadFile(out) + if err != nil { + t.Errorf("%q: unable to read file; %v", g.in, err) + continue + } + got := file.String() + // Remove trailing newline. + want := string(bytes.TrimSpace(buf)) + if got != want { + t.Errorf("%q: graph mismatch; expected %q, got %q", g.in, want, got) + } + } +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/lexer/testdata/tokens.dot b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/testdata/tokens.dot new file mode 100644 index 000000000000..3a1619518d40 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/testdata/tokens.dot @@ -0,0 +1,39 @@ +# C preprocessing directives act as comments. +/* block comment */ +// keywords are case-insensitive. +graph { + node [] + Node [] + NODE [] + edge [] + Edge [] + EDGE [] + subgraph {} + subGraph {} + Subgraph {} + SubGraph {} + SUBGRAPH S {} + A; B [style=filled, fillcolor=red] + C:nw -- D:se + "foo" + .10 + -20 + 3.14 + F [label=<
foo
>] + _foo + a10 +} +Graph { +} +GRAPH { +} +digraph { +} +Digraph { +} +diGraph { +} +DiGraph { +} +DIGRAPH { +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/lexer/testdata/tokens.golden b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/testdata/tokens.golden new file mode 100644 index 000000000000..f64bdefcaa52 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/testdata/tokens.golden @@ -0,0 +1,37 @@ +graph { + node [] + node [] + node [] + edge [] + edge [] + edge [] + {} + {} + {} + {} + subgraph S {} + A + B [style=filled fillcolor=red] + C:nw -- D:se + "foo" + .10 + -20 + 3.14 + F [label=<
foo
>] + _foo + a10 +} +graph { +} +graph { +} +digraph { +} +digraph { +} +digraph { +} +digraph { +} +digraph { +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/lexer/transitiontable.go b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/transitiontable.go new file mode 100644 index 000000000000..98c1d0519d81 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/lexer/transitiontable.go @@ -0,0 +1,3027 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package lexer + +/* +Let s be the current state +Let r be the current input rune +transitionTable[s](r) returns the next state. +*/ +type TransitionTable [NumStates]func(rune) int + +var TransTab = TransitionTable{ + + // S0 + func(r rune) int { + switch { + case r == 9: // ['\t','\t'] + return 1 + case r == 10: // ['\n','\n'] + return 1 + case r == 13: // ['\r','\r'] + return 1 + case r == 32: // [' ',' '] + return 1 + case r == 34: // ['"','"'] + return 2 + case r == 35: // ['#','#'] + return 3 + case r == 44: // [',',','] + return 4 + case r == 45: // ['-','-'] + return 5 + case r == 46: // ['.','.'] + return 6 + case r == 47: // ['/','/'] + return 7 + case 48 <= r && r <= 57: // ['0','9'] + return 8 + case r == 58: // [':',':'] + return 9 + case r == 59: // [';',';'] + return 10 + case r == 60: // ['<','<'] + return 11 + case r == 61: // ['=','='] + return 12 + case 65 <= r && r <= 67: // ['A','C'] + return 13 + case r == 68: // ['D','D'] + return 14 + case r == 69: // ['E','E'] + return 15 + case r == 70: // ['F','F'] + return 13 + case r == 71: // ['G','G'] + return 16 + case 72 <= r && r <= 77: // ['H','M'] + return 13 + case r == 78: // ['N','N'] + return 17 + case 79 <= r && r <= 82: // ['O','R'] + return 13 + case r == 83: // ['S','S'] + return 18 + case 84 <= r && r <= 90: // ['T','Z'] + return 13 + case r == 91: // ['[','['] + return 19 + case r == 93: // [']',']'] + return 20 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 99: // ['a','c'] + return 13 + case r == 100: // ['d','d'] + return 22 + case r == 101: // ['e','e'] + return 23 + case r == 102: // ['f','f'] + return 13 + case r == 103: // ['g','g'] + return 24 + case 104 <= r && r <= 109: // ['h','m'] + return 13 + case r == 110: // ['n','n'] + return 25 + case 111 <= r && r <= 114: // ['o','r'] + return 13 + case r == 115: // ['s','s'] + return 26 + case 116 <= r && r <= 122: // ['t','z'] + return 13 + case r == 123: // ['{','{'] + return 27 + case r == 125: // ['}','}'] + return 28 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S1 + func(r rune) int { + switch { + + } + return NoState + }, + + // S2 + func(r rune) int { + switch { + case 1 <= r && r <= 33: // [\u0001,'!'] + return 30 + case r == 34: // ['"','"'] + return 31 + case 35 <= r && r <= 91: // ['#','['] + return 30 + case r == 92: // ['\','\'] + return 32 + case 93 <= r && r <= 127: // [']',\u007f] + return 30 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 33 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 33 + + } + return NoState + }, + + // S3 + func(r rune) int { + switch { + case r == 10: // ['\n','\n'] + return 34 + + default: + return 3 + } + + }, + + // S4 + func(r rune) int { + switch { + + } + return NoState + }, + + // S5 + func(r rune) int { + switch { + case r == 45: // ['-','-'] + return 35 + case r == 46: // ['.','.'] + return 6 + case 48 <= r && r <= 57: // ['0','9'] + return 8 + case r == 62: // ['>','>'] + return 36 + + } + return NoState + }, + + // S6 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 37 + + } + return NoState + }, + + // S7 + func(r rune) int { + switch { + case r == 42: // ['*','*'] + return 38 + case r == 47: // ['/','/'] + return 39 + + } + return NoState + }, + + // S8 + func(r rune) int { + switch { + case r == 46: // ['.','.'] + return 40 + case 48 <= r && r <= 57: // ['0','9'] + return 8 + + } + return NoState + }, + + // S9 + func(r rune) int { + switch { + + } + return NoState + }, + + // S10 + func(r rune) int { + switch { + + } + return NoState + }, + + // S11 + func(r rune) int { + switch { + case 1 <= r && r <= 59: // [\u0001,';'] + return 41 + case r == 60: // ['<','<'] + return 42 + case r == 61: // ['=','='] + return 41 + case r == 62: // ['>','>'] + return 43 + case 63 <= r && r <= 255: // ['?',\u00ff] + return 41 + + } + return NoState + }, + + // S12 + func(r rune) int { + switch { + + } + return NoState + }, + + // S13 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S14 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 72: // ['A','H'] + return 13 + case r == 73: // ['I','I'] + return 45 + case 74 <= r && r <= 90: // ['J','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 104: // ['a','h'] + return 13 + case r == 105: // ['i','i'] + return 46 + case 106 <= r && r <= 122: // ['j','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S15 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 67: // ['A','C'] + return 13 + case r == 68: // ['D','D'] + return 47 + case 69 <= r && r <= 90: // ['E','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 99: // ['a','c'] + return 13 + case r == 100: // ['d','d'] + return 48 + case 101 <= r && r <= 122: // ['e','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S16 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 81: // ['A','Q'] + return 13 + case r == 82: // ['R','R'] + return 49 + case 83 <= r && r <= 90: // ['S','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 50 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S17 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 78: // ['A','N'] + return 13 + case r == 79: // ['O','O'] + return 51 + case 80 <= r && r <= 90: // ['P','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 110: // ['a','n'] + return 13 + case r == 111: // ['o','o'] + return 52 + case 112 <= r && r <= 122: // ['p','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S18 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 83: // ['A','S'] + return 13 + case r == 84: // ['T','T'] + return 53 + case r == 85: // ['U','U'] + return 54 + case 86 <= r && r <= 90: // ['V','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 115: // ['a','s'] + return 13 + case r == 116: // ['t','t'] + return 55 + case r == 117: // ['u','u'] + return 56 + case 118 <= r && r <= 122: // ['v','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S19 + func(r rune) int { + switch { + + } + return NoState + }, + + // S20 + func(r rune) int { + switch { + + } + return NoState + }, + + // S21 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S22 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 104: // ['a','h'] + return 13 + case r == 105: // ['i','i'] + return 57 + case 106 <= r && r <= 122: // ['j','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S23 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 99: // ['a','c'] + return 13 + case r == 100: // ['d','d'] + return 58 + case 101 <= r && r <= 122: // ['e','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S24 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 59 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S25 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 110: // ['a','n'] + return 13 + case r == 111: // ['o','o'] + return 60 + case 112 <= r && r <= 122: // ['p','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S26 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 115: // ['a','s'] + return 13 + case r == 116: // ['t','t'] + return 61 + case r == 117: // ['u','u'] + return 62 + case 118 <= r && r <= 122: // ['v','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S27 + func(r rune) int { + switch { + + } + return NoState + }, + + // S28 + func(r rune) int { + switch { + + } + return NoState + }, + + // S29 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S30 + func(r rune) int { + switch { + case 1 <= r && r <= 33: // [\u0001,'!'] + return 30 + case r == 34: // ['"','"'] + return 31 + case 35 <= r && r <= 91: // ['#','['] + return 30 + case r == 92: // ['\','\'] + return 32 + case 93 <= r && r <= 127: // [']',\u007f] + return 30 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 33 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 33 + + } + return NoState + }, + + // S31 + func(r rune) int { + switch { + + } + return NoState + }, + + // S32 + func(r rune) int { + switch { + case 1 <= r && r <= 33: // [\u0001,'!'] + return 63 + case r == 34: // ['"','"'] + return 64 + case 35 <= r && r <= 91: // ['#','['] + return 63 + case r == 92: // ['\','\'] + return 64 + case 93 <= r && r <= 127: // [']',\u007f] + return 63 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 65 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 65 + + } + return NoState + }, + + // S33 + func(r rune) int { + switch { + case 1 <= r && r <= 33: // [\u0001,'!'] + return 30 + case r == 34: // ['"','"'] + return 31 + case 35 <= r && r <= 91: // ['#','['] + return 30 + case r == 92: // ['\','\'] + return 32 + case 93 <= r && r <= 127: // [']',\u007f] + return 30 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 33 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 33 + + } + return NoState + }, + + // S34 + func(r rune) int { + switch { + + } + return NoState + }, + + // S35 + func(r rune) int { + switch { + + } + return NoState + }, + + // S36 + func(r rune) int { + switch { + + } + return NoState + }, + + // S37 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 37 + + } + return NoState + }, + + // S38 + func(r rune) int { + switch { + case r == 42: // ['*','*'] + return 66 + + default: + return 38 + } + + }, + + // S39 + func(r rune) int { + switch { + case r == 10: // ['\n','\n'] + return 34 + + default: + return 39 + } + + }, + + // S40 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 67 + + } + return NoState + }, + + // S41 + func(r rune) int { + switch { + case 1 <= r && r <= 59: // [\u0001,';'] + return 41 + case r == 60: // ['<','<'] + return 42 + case r == 61: // ['=','='] + return 41 + case r == 62: // ['>','>'] + return 43 + case 63 <= r && r <= 255: // ['?',\u00ff] + return 41 + + } + return NoState + }, + + // S42 + func(r rune) int { + switch { + case 1 <= r && r <= 59: // [\u0001,';'] + return 68 + case r == 61: // ['=','='] + return 68 + case 63 <= r && r <= 255: // ['?',\u00ff] + return 68 + + } + return NoState + }, + + // S43 + func(r rune) int { + switch { + + } + return NoState + }, + + // S44 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S45 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 70: // ['A','F'] + return 13 + case r == 71: // ['G','G'] + return 69 + case 72 <= r && r <= 90: // ['H','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S46 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 70: // ['A','F'] + return 13 + case r == 71: // ['G','G'] + return 70 + case 72 <= r && r <= 90: // ['H','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 102: // ['a','f'] + return 13 + case r == 103: // ['g','g'] + return 71 + case 104 <= r && r <= 122: // ['h','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S47 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 70: // ['A','F'] + return 13 + case r == 71: // ['G','G'] + return 72 + case 72 <= r && r <= 90: // ['H','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S48 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 102: // ['a','f'] + return 13 + case r == 103: // ['g','g'] + return 73 + case 104 <= r && r <= 122: // ['h','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S49 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case r == 65: // ['A','A'] + return 74 + case 66 <= r && r <= 90: // ['B','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S50 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 75 + case 98 <= r && r <= 122: // ['b','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S51 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 67: // ['A','C'] + return 13 + case r == 68: // ['D','D'] + return 76 + case 69 <= r && r <= 90: // ['E','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S52 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 99: // ['a','c'] + return 13 + case r == 100: // ['d','d'] + return 77 + case 101 <= r && r <= 122: // ['e','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S53 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 81: // ['A','Q'] + return 13 + case r == 82: // ['R','R'] + return 78 + case 83 <= r && r <= 90: // ['S','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S54 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case r == 65: // ['A','A'] + return 13 + case r == 66: // ['B','B'] + return 79 + case 67 <= r && r <= 90: // ['C','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S55 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 80 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S56 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 13 + case r == 98: // ['b','b'] + return 81 + case 99 <= r && r <= 122: // ['c','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S57 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 70: // ['A','F'] + return 13 + case r == 71: // ['G','G'] + return 82 + case 72 <= r && r <= 90: // ['H','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 102: // ['a','f'] + return 13 + case r == 103: // ['g','g'] + return 83 + case 104 <= r && r <= 122: // ['h','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S58 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 102: // ['a','f'] + return 13 + case r == 103: // ['g','g'] + return 84 + case 104 <= r && r <= 122: // ['h','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S59 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 85 + case 98 <= r && r <= 122: // ['b','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S60 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 99: // ['a','c'] + return 13 + case r == 100: // ['d','d'] + return 86 + case 101 <= r && r <= 122: // ['e','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S61 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 87 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S62 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 13 + case r == 98: // ['b','b'] + return 88 + case 99 <= r && r <= 122: // ['c','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S63 + func(r rune) int { + switch { + case 1 <= r && r <= 33: // [\u0001,'!'] + return 30 + case r == 34: // ['"','"'] + return 31 + case 35 <= r && r <= 91: // ['#','['] + return 30 + case r == 92: // ['\','\'] + return 32 + case 93 <= r && r <= 127: // [']',\u007f] + return 30 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 33 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 33 + + } + return NoState + }, + + // S64 + func(r rune) int { + switch { + case 1 <= r && r <= 33: // [\u0001,'!'] + return 30 + case r == 34: // ['"','"'] + return 31 + case 35 <= r && r <= 91: // ['#','['] + return 30 + case r == 92: // ['\','\'] + return 32 + case 93 <= r && r <= 127: // [']',\u007f] + return 30 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 33 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 33 + + } + return NoState + }, + + // S65 + func(r rune) int { + switch { + case 1 <= r && r <= 33: // [\u0001,'!'] + return 30 + case r == 34: // ['"','"'] + return 31 + case 35 <= r && r <= 91: // ['#','['] + return 30 + case r == 92: // ['\','\'] + return 32 + case 93 <= r && r <= 127: // [']',\u007f] + return 30 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 33 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 33 + + } + return NoState + }, + + // S66 + func(r rune) int { + switch { + case r == 42: // ['*','*'] + return 66 + case r == 47: // ['/','/'] + return 89 + + default: + return 38 + } + + }, + + // S67 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 67 + + } + return NoState + }, + + // S68 + func(r rune) int { + switch { + case 1 <= r && r <= 59: // [\u0001,';'] + return 68 + case r == 61: // ['=','='] + return 68 + case r == 62: // ['>','>'] + return 90 + case 63 <= r && r <= 255: // ['?',\u00ff] + return 68 + + } + return NoState + }, + + // S69 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 81: // ['A','Q'] + return 13 + case r == 82: // ['R','R'] + return 91 + case 83 <= r && r <= 90: // ['S','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S70 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 92 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S71 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 93 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S72 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 68: // ['A','D'] + return 13 + case r == 69: // ['E','E'] + return 94 + case 70 <= r && r <= 90: // ['F','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S73 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 100: // ['a','d'] + return 13 + case r == 101: // ['e','e'] + return 94 + case 102 <= r && r <= 122: // ['f','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S74 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 79: // ['A','O'] + return 13 + case r == 80: // ['P','P'] + return 95 + case 81 <= r && r <= 90: // ['Q','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S75 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 111: // ['a','o'] + return 13 + case r == 112: // ['p','p'] + return 96 + case 113 <= r && r <= 122: // ['q','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S76 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 68: // ['A','D'] + return 13 + case r == 69: // ['E','E'] + return 97 + case 70 <= r && r <= 90: // ['F','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S77 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 100: // ['a','d'] + return 13 + case r == 101: // ['e','e'] + return 97 + case 102 <= r && r <= 122: // ['f','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S78 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 72: // ['A','H'] + return 13 + case r == 73: // ['I','I'] + return 98 + case 74 <= r && r <= 90: // ['J','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S79 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 70: // ['A','F'] + return 13 + case r == 71: // ['G','G'] + return 99 + case 72 <= r && r <= 90: // ['H','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S80 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 104: // ['a','h'] + return 13 + case r == 105: // ['i','i'] + return 100 + case 106 <= r && r <= 122: // ['j','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S81 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 70: // ['A','F'] + return 13 + case r == 71: // ['G','G'] + return 101 + case 72 <= r && r <= 90: // ['H','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 102: // ['a','f'] + return 13 + case r == 103: // ['g','g'] + return 102 + case 104 <= r && r <= 122: // ['h','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S82 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 103 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S83 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 104 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S84 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 100: // ['a','d'] + return 13 + case r == 101: // ['e','e'] + return 94 + case 102 <= r && r <= 122: // ['f','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S85 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 111: // ['a','o'] + return 13 + case r == 112: // ['p','p'] + return 105 + case 113 <= r && r <= 122: // ['q','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S86 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 100: // ['a','d'] + return 13 + case r == 101: // ['e','e'] + return 97 + case 102 <= r && r <= 122: // ['f','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S87 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 104: // ['a','h'] + return 13 + case r == 105: // ['i','i'] + return 106 + case 106 <= r && r <= 122: // ['j','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S88 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 70: // ['A','F'] + return 13 + case r == 71: // ['G','G'] + return 107 + case 72 <= r && r <= 90: // ['H','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 102: // ['a','f'] + return 13 + case r == 103: // ['g','g'] + return 108 + case 104 <= r && r <= 122: // ['h','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S89 + func(r rune) int { + switch { + + } + return NoState + }, + + // S90 + func(r rune) int { + switch { + case 1 <= r && r <= 59: // [\u0001,';'] + return 41 + case r == 60: // ['<','<'] + return 42 + case r == 61: // ['=','='] + return 41 + case r == 62: // ['>','>'] + return 43 + case 63 <= r && r <= 255: // ['?',\u00ff] + return 41 + + } + return NoState + }, + + // S91 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case r == 65: // ['A','A'] + return 109 + case 66 <= r && r <= 90: // ['B','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S92 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 110 + case 98 <= r && r <= 122: // ['b','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S93 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 111 + case 98 <= r && r <= 122: // ['b','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S94 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S95 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 71: // ['A','G'] + return 13 + case r == 72: // ['H','H'] + return 112 + case 73 <= r && r <= 90: // ['I','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S96 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 103: // ['a','g'] + return 13 + case r == 104: // ['h','h'] + return 112 + case 105 <= r && r <= 122: // ['i','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S97 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S98 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 66: // ['A','B'] + return 13 + case r == 67: // ['C','C'] + return 113 + case 68 <= r && r <= 90: // ['D','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S99 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 81: // ['A','Q'] + return 13 + case r == 82: // ['R','R'] + return 114 + case 83 <= r && r <= 90: // ['S','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S100 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 98: // ['a','b'] + return 13 + case r == 99: // ['c','c'] + return 115 + case 100 <= r && r <= 122: // ['d','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S101 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 116 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S102 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 117 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S103 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 118 + case 98 <= r && r <= 122: // ['b','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S104 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 119 + case 98 <= r && r <= 122: // ['b','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S105 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 103: // ['a','g'] + return 13 + case r == 104: // ['h','h'] + return 112 + case 105 <= r && r <= 122: // ['i','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S106 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 98: // ['a','b'] + return 13 + case r == 99: // ['c','c'] + return 120 + case 100 <= r && r <= 122: // ['d','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S107 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 121 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S108 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 113: // ['a','q'] + return 13 + case r == 114: // ['r','r'] + return 122 + case 115 <= r && r <= 122: // ['s','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S109 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 79: // ['A','O'] + return 13 + case r == 80: // ['P','P'] + return 123 + case 81 <= r && r <= 90: // ['Q','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S110 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 111: // ['a','o'] + return 13 + case r == 112: // ['p','p'] + return 124 + case 113 <= r && r <= 122: // ['q','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S111 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 111: // ['a','o'] + return 13 + case r == 112: // ['p','p'] + return 125 + case 113 <= r && r <= 122: // ['q','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S112 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S113 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 83: // ['A','S'] + return 13 + case r == 84: // ['T','T'] + return 126 + case 85 <= r && r <= 90: // ['U','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S114 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case r == 65: // ['A','A'] + return 127 + case 66 <= r && r <= 90: // ['B','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S115 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 115: // ['a','s'] + return 13 + case r == 116: // ['t','t'] + return 126 + case 117 <= r && r <= 122: // ['u','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S116 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 128 + case 98 <= r && r <= 122: // ['b','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S117 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 129 + case 98 <= r && r <= 122: // ['b','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S118 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 111: // ['a','o'] + return 13 + case r == 112: // ['p','p'] + return 130 + case 113 <= r && r <= 122: // ['q','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S119 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 111: // ['a','o'] + return 13 + case r == 112: // ['p','p'] + return 131 + case 113 <= r && r <= 122: // ['q','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S120 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 115: // ['a','s'] + return 13 + case r == 116: // ['t','t'] + return 126 + case 117 <= r && r <= 122: // ['u','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S121 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 132 + case 98 <= r && r <= 122: // ['b','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S122 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case r == 97: // ['a','a'] + return 133 + case 98 <= r && r <= 122: // ['b','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S123 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 71: // ['A','G'] + return 13 + case r == 72: // ['H','H'] + return 134 + case 73 <= r && r <= 90: // ['I','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S124 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 103: // ['a','g'] + return 13 + case r == 104: // ['h','h'] + return 134 + case 105 <= r && r <= 122: // ['i','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S125 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 103: // ['a','g'] + return 13 + case r == 104: // ['h','h'] + return 134 + case 105 <= r && r <= 122: // ['i','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S126 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S127 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 79: // ['A','O'] + return 13 + case r == 80: // ['P','P'] + return 135 + case 81 <= r && r <= 90: // ['Q','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S128 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 111: // ['a','o'] + return 13 + case r == 112: // ['p','p'] + return 136 + case 113 <= r && r <= 122: // ['q','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S129 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 111: // ['a','o'] + return 13 + case r == 112: // ['p','p'] + return 137 + case 113 <= r && r <= 122: // ['q','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S130 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 103: // ['a','g'] + return 13 + case r == 104: // ['h','h'] + return 134 + case 105 <= r && r <= 122: // ['i','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S131 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 103: // ['a','g'] + return 13 + case r == 104: // ['h','h'] + return 134 + case 105 <= r && r <= 122: // ['i','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S132 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 111: // ['a','o'] + return 13 + case r == 112: // ['p','p'] + return 138 + case 113 <= r && r <= 122: // ['q','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S133 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 111: // ['a','o'] + return 13 + case r == 112: // ['p','p'] + return 139 + case 113 <= r && r <= 122: // ['q','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S134 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S135 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 71: // ['A','G'] + return 13 + case r == 72: // ['H','H'] + return 140 + case 73 <= r && r <= 90: // ['I','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S136 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 103: // ['a','g'] + return 13 + case r == 104: // ['h','h'] + return 140 + case 105 <= r && r <= 122: // ['i','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S137 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 103: // ['a','g'] + return 13 + case r == 104: // ['h','h'] + return 140 + case 105 <= r && r <= 122: // ['i','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S138 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 103: // ['a','g'] + return 13 + case r == 104: // ['h','h'] + return 140 + case 105 <= r && r <= 122: // ['i','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S139 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 103: // ['a','g'] + return 13 + case r == 104: // ['h','h'] + return 140 + case 105 <= r && r <= 122: // ['i','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, + + // S140 + func(r rune) int { + switch { + case 48 <= r && r <= 57: // ['0','9'] + return 44 + case 65 <= r && r <= 90: // ['A','Z'] + return 13 + case r == 95: // ['_','_'] + return 21 + case 97 <= r && r <= 122: // ['a','z'] + return 13 + case 128 <= r && r <= 65532: // [\u0080,\ufffc] + return 29 + case 65534 <= r && r <= 1114111: // [\ufffe,\U0010ffff] + return 29 + + } + return NoState + }, +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/parser/action.go b/vendor/github.com/gonum/graph/formats/dot/internal/parser/action.go new file mode 100644 index 000000000000..10d7eccbed79 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/parser/action.go @@ -0,0 +1,61 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package parser + +import ( + "fmt" +) + +type action interface { + act() + String() string +} + +type ( + accept bool + shift int // value is next state index + reduce int // value is production index +) + +func (this accept) act() {} +func (this shift) act() {} +func (this reduce) act() {} + +func (this accept) Equal(that action) bool { + if _, ok := that.(accept); ok { + return true + } + return false +} + +func (this reduce) Equal(that action) bool { + that1, ok := that.(reduce) + if !ok { + return false + } + return this == that1 +} + +func (this shift) Equal(that action) bool { + that1, ok := that.(shift) + if !ok { + return false + } + return this == that1 +} + +func (this accept) String() string { return "accept(0)" } +func (this shift) String() string { return fmt.Sprintf("shift:%d", this) } +func (this reduce) String() string { + return fmt.Sprintf("reduce:%d(%s)", this, productionsTable[this].String) +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/parser/actiontable.go b/vendor/github.com/gonum/graph/formats/dot/internal/parser/actiontable.go new file mode 100644 index 000000000000..bffc8f3c3cce --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/parser/actiontable.go @@ -0,0 +1,2286 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package parser + +type ( + actionTable [numStates]actionRow + actionRow struct { + canRecover bool + actions [numSymbols]action + } +) + +var actionTab = actionTable{ + actionRow{ // S0 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + shift(4), /* strict */ + reduce(4), /* graphx, reduce: OptStrict */ + reduce(4), /* digraph, reduce: OptStrict */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S1 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + accept(true), /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + shift(4), /* strict */ + reduce(4), /* graphx, reduce: OptStrict */ + reduce(4), /* digraph, reduce: OptStrict */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S2 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + reduce(1), /* $, reduce: File */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + reduce(1), /* strict, reduce: File */ + reduce(1), /* graphx, reduce: File */ + reduce(1), /* digraph, reduce: File */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S3 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + shift(7), /* graphx */ + shift(8), /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S4 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + reduce(5), /* graphx, reduce: OptStrict */ + reduce(5), /* digraph, reduce: OptStrict */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S5 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + reduce(2), /* $, reduce: File */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + reduce(2), /* strict, reduce: File */ + reduce(2), /* graphx, reduce: File */ + reduce(2), /* digraph, reduce: File */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S6 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(53), /* {, reduce: OptID */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + shift(11), /* id */ + + }, + }, + actionRow{ // S7 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(6), /* {, reduce: DirectedGraph */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + reduce(6), /* id, reduce: DirectedGraph */ + + }, + }, + actionRow{ // S8 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(7), /* {, reduce: DirectedGraph */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + reduce(7), /* id, reduce: DirectedGraph */ + + }, + }, + actionRow{ // S9 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + shift(12), /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S10 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(54), /* {, reduce: OptID */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S11 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(52), /* {, reduce: ID */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S12 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(43), /* {, reduce: OptSubgraphID */ + reduce(10), /* }, reduce: OptStmtList */ + nil, /* empty */ + nil, /* strict */ + shift(14), /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + shift(25), /* node */ + shift(26), /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + shift(29), /* subgraph */ + nil, /* : */ + shift(30), /* id */ + + }, + }, + actionRow{ // S13 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + shift(31), /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S14 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + reduce(27), /* [, reduce: Component */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S15 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(43), /* {, reduce: OptSubgraphID */ + reduce(11), /* }, reduce: OptStmtList */ + nil, /* empty */ + nil, /* strict */ + shift(14), /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + shift(25), /* node */ + shift(26), /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + shift(29), /* subgraph */ + nil, /* : */ + shift(30), /* id */ + + }, + }, + actionRow{ // S16 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(17), /* {, reduce: OptSemi */ + reduce(17), /* }, reduce: OptSemi */ + nil, /* empty */ + nil, /* strict */ + reduce(17), /* graphx, reduce: OptSemi */ + nil, /* digraph */ + shift(34), /* ; */ + nil, /* -- */ + nil, /* -> */ + reduce(17), /* node, reduce: OptSemi */ + reduce(17), /* edge, reduce: OptSemi */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(17), /* subgraph, reduce: OptSemi */ + nil, /* : */ + reduce(17), /* id, reduce: OptSemi */ + + }, + }, + actionRow{ // S17 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(12), /* {, reduce: Stmt */ + reduce(12), /* }, reduce: Stmt */ + nil, /* empty */ + nil, /* strict */ + reduce(12), /* graphx, reduce: Stmt */ + nil, /* digraph */ + reduce(12), /* ;, reduce: Stmt */ + nil, /* -- */ + nil, /* -> */ + reduce(12), /* node, reduce: Stmt */ + reduce(12), /* edge, reduce: Stmt */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(12), /* subgraph, reduce: Stmt */ + nil, /* : */ + reduce(12), /* id, reduce: Stmt */ + + }, + }, + actionRow{ // S18 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(13), /* {, reduce: Stmt */ + reduce(13), /* }, reduce: Stmt */ + nil, /* empty */ + nil, /* strict */ + reduce(13), /* graphx, reduce: Stmt */ + nil, /* digraph */ + reduce(13), /* ;, reduce: Stmt */ + nil, /* -- */ + nil, /* -> */ + reduce(13), /* node, reduce: Stmt */ + reduce(13), /* edge, reduce: Stmt */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(13), /* subgraph, reduce: Stmt */ + nil, /* : */ + reduce(13), /* id, reduce: Stmt */ + + }, + }, + actionRow{ // S19 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(14), /* {, reduce: Stmt */ + reduce(14), /* }, reduce: Stmt */ + nil, /* empty */ + nil, /* strict */ + reduce(14), /* graphx, reduce: Stmt */ + nil, /* digraph */ + reduce(14), /* ;, reduce: Stmt */ + nil, /* -- */ + nil, /* -> */ + reduce(14), /* node, reduce: Stmt */ + reduce(14), /* edge, reduce: Stmt */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(14), /* subgraph, reduce: Stmt */ + nil, /* : */ + reduce(14), /* id, reduce: Stmt */ + + }, + }, + actionRow{ // S20 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(15), /* {, reduce: Stmt */ + reduce(15), /* }, reduce: Stmt */ + nil, /* empty */ + nil, /* strict */ + reduce(15), /* graphx, reduce: Stmt */ + nil, /* digraph */ + reduce(15), /* ;, reduce: Stmt */ + nil, /* -- */ + nil, /* -> */ + reduce(15), /* node, reduce: Stmt */ + reduce(15), /* edge, reduce: Stmt */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(15), /* subgraph, reduce: Stmt */ + nil, /* : */ + reduce(15), /* id, reduce: Stmt */ + + }, + }, + actionRow{ // S21 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(16), /* {, reduce: Stmt */ + reduce(16), /* }, reduce: Stmt */ + nil, /* empty */ + nil, /* strict */ + reduce(16), /* graphx, reduce: Stmt */ + nil, /* digraph */ + reduce(16), /* ;, reduce: Stmt */ + reduce(46), /* --, reduce: Vertex */ + reduce(46), /* ->, reduce: Vertex */ + reduce(16), /* node, reduce: Stmt */ + reduce(16), /* edge, reduce: Stmt */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(16), /* subgraph, reduce: Stmt */ + nil, /* : */ + reduce(16), /* id, reduce: Stmt */ + + }, + }, + actionRow{ // S22 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(32), /* {, reduce: OptAttrList */ + reduce(32), /* }, reduce: OptAttrList */ + nil, /* empty */ + nil, /* strict */ + reduce(32), /* graphx, reduce: OptAttrList */ + nil, /* digraph */ + reduce(32), /* ;, reduce: OptAttrList */ + reduce(45), /* --, reduce: Vertex */ + reduce(45), /* ->, reduce: Vertex */ + reduce(32), /* node, reduce: OptAttrList */ + reduce(32), /* edge, reduce: OptAttrList */ + shift(37), /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(32), /* subgraph, reduce: OptAttrList */ + nil, /* : */ + reduce(32), /* id, reduce: OptAttrList */ + + }, + }, + actionRow{ // S23 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + shift(40), /* -- */ + shift(41), /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S24 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + shift(37), /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S25 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + reduce(28), /* [, reduce: Component */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S26 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + reduce(29), /* [, reduce: Component */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S27 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(50), /* {, reduce: OptPort */ + reduce(50), /* }, reduce: OptPort */ + nil, /* empty */ + nil, /* strict */ + reduce(50), /* graphx, reduce: OptPort */ + nil, /* digraph */ + reduce(50), /* ;, reduce: OptPort */ + reduce(50), /* --, reduce: OptPort */ + reduce(50), /* ->, reduce: OptPort */ + reduce(50), /* node, reduce: OptPort */ + reduce(50), /* edge, reduce: OptPort */ + reduce(50), /* [, reduce: OptPort */ + nil, /* ] */ + nil, /* , */ + shift(43), /* = */ + reduce(50), /* subgraph, reduce: OptPort */ + shift(46), /* : */ + reduce(50), /* id, reduce: OptPort */ + + }, + }, + actionRow{ // S28 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + shift(47), /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S29 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(53), /* {, reduce: OptID */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + shift(11), /* id */ + + }, + }, + actionRow{ // S30 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(52), /* {, reduce: ID */ + reduce(52), /* }, reduce: ID */ + nil, /* empty */ + nil, /* strict */ + reduce(52), /* graphx, reduce: ID */ + nil, /* digraph */ + reduce(52), /* ;, reduce: ID */ + reduce(52), /* --, reduce: ID */ + reduce(52), /* ->, reduce: ID */ + reduce(52), /* node, reduce: ID */ + reduce(52), /* edge, reduce: ID */ + reduce(52), /* [, reduce: ID */ + nil, /* ] */ + nil, /* , */ + reduce(52), /* =, reduce: ID */ + reduce(52), /* subgraph, reduce: ID */ + reduce(52), /* :, reduce: ID */ + reduce(52), /* id, reduce: ID */ + + }, + }, + actionRow{ // S31 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + reduce(3), /* $, reduce: Graph */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + reduce(3), /* strict, reduce: Graph */ + reduce(3), /* graphx, reduce: Graph */ + reduce(3), /* digraph, reduce: Graph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S32 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(17), /* {, reduce: OptSemi */ + reduce(17), /* }, reduce: OptSemi */ + nil, /* empty */ + nil, /* strict */ + reduce(17), /* graphx, reduce: OptSemi */ + nil, /* digraph */ + shift(34), /* ; */ + nil, /* -- */ + nil, /* -> */ + reduce(17), /* node, reduce: OptSemi */ + reduce(17), /* edge, reduce: OptSemi */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(17), /* subgraph, reduce: OptSemi */ + nil, /* : */ + reduce(17), /* id, reduce: OptSemi */ + + }, + }, + actionRow{ // S33 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(8), /* {, reduce: StmtList */ + reduce(8), /* }, reduce: StmtList */ + nil, /* empty */ + nil, /* strict */ + reduce(8), /* graphx, reduce: StmtList */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + reduce(8), /* node, reduce: StmtList */ + reduce(8), /* edge, reduce: StmtList */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(8), /* subgraph, reduce: StmtList */ + nil, /* : */ + reduce(8), /* id, reduce: StmtList */ + + }, + }, + actionRow{ // S34 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(18), /* {, reduce: OptSemi */ + reduce(18), /* }, reduce: OptSemi */ + nil, /* empty */ + nil, /* strict */ + reduce(18), /* graphx, reduce: OptSemi */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + reduce(18), /* node, reduce: OptSemi */ + reduce(18), /* edge, reduce: OptSemi */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(18), /* subgraph, reduce: OptSemi */ + nil, /* : */ + reduce(18), /* id, reduce: OptSemi */ + + }, + }, + actionRow{ // S35 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(19), /* {, reduce: NodeStmt */ + reduce(19), /* }, reduce: NodeStmt */ + nil, /* empty */ + nil, /* strict */ + reduce(19), /* graphx, reduce: NodeStmt */ + nil, /* digraph */ + reduce(19), /* ;, reduce: NodeStmt */ + nil, /* -- */ + nil, /* -> */ + reduce(19), /* node, reduce: NodeStmt */ + reduce(19), /* edge, reduce: NodeStmt */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(19), /* subgraph, reduce: NodeStmt */ + nil, /* : */ + reduce(19), /* id, reduce: NodeStmt */ + + }, + }, + actionRow{ // S36 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(33), /* {, reduce: OptAttrList */ + reduce(33), /* }, reduce: OptAttrList */ + nil, /* empty */ + nil, /* strict */ + reduce(33), /* graphx, reduce: OptAttrList */ + nil, /* digraph */ + reduce(33), /* ;, reduce: OptAttrList */ + nil, /* -- */ + nil, /* -> */ + reduce(33), /* node, reduce: OptAttrList */ + reduce(33), /* edge, reduce: OptAttrList */ + shift(50), /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(33), /* subgraph, reduce: OptAttrList */ + nil, /* : */ + reduce(33), /* id, reduce: OptAttrList */ + + }, + }, + actionRow{ // S37 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + reduce(36), /* ], reduce: OptAList */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + shift(55), /* id */ + + }, + }, + actionRow{ // S38 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(32), /* {, reduce: OptAttrList */ + reduce(32), /* }, reduce: OptAttrList */ + nil, /* empty */ + nil, /* strict */ + reduce(32), /* graphx, reduce: OptAttrList */ + nil, /* digraph */ + reduce(32), /* ;, reduce: OptAttrList */ + nil, /* -- */ + nil, /* -> */ + reduce(32), /* node, reduce: OptAttrList */ + reduce(32), /* edge, reduce: OptAttrList */ + shift(37), /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(32), /* subgraph, reduce: OptAttrList */ + nil, /* : */ + reduce(32), /* id, reduce: OptAttrList */ + + }, + }, + actionRow{ // S39 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(43), /* {, reduce: OptSubgraphID */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + shift(29), /* subgraph */ + nil, /* : */ + shift(62), /* id */ + + }, + }, + actionRow{ // S40 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(22), /* {, reduce: DirectedEdge */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(22), /* subgraph, reduce: DirectedEdge */ + nil, /* : */ + reduce(22), /* id, reduce: DirectedEdge */ + + }, + }, + actionRow{ // S41 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(23), /* {, reduce: DirectedEdge */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(23), /* subgraph, reduce: DirectedEdge */ + nil, /* : */ + reduce(23), /* id, reduce: DirectedEdge */ + + }, + }, + actionRow{ // S42 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(26), /* {, reduce: AttrStmt */ + reduce(26), /* }, reduce: AttrStmt */ + nil, /* empty */ + nil, /* strict */ + reduce(26), /* graphx, reduce: AttrStmt */ + nil, /* digraph */ + reduce(26), /* ;, reduce: AttrStmt */ + nil, /* -- */ + nil, /* -> */ + reduce(26), /* node, reduce: AttrStmt */ + reduce(26), /* edge, reduce: AttrStmt */ + shift(50), /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(26), /* subgraph, reduce: AttrStmt */ + nil, /* : */ + reduce(26), /* id, reduce: AttrStmt */ + + }, + }, + actionRow{ // S43 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + shift(64), /* id */ + + }, + }, + actionRow{ // S44 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(47), /* {, reduce: Node */ + reduce(47), /* }, reduce: Node */ + nil, /* empty */ + nil, /* strict */ + reduce(47), /* graphx, reduce: Node */ + nil, /* digraph */ + reduce(47), /* ;, reduce: Node */ + reduce(47), /* --, reduce: Node */ + reduce(47), /* ->, reduce: Node */ + reduce(47), /* node, reduce: Node */ + reduce(47), /* edge, reduce: Node */ + reduce(47), /* [, reduce: Node */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(47), /* subgraph, reduce: Node */ + nil, /* : */ + reduce(47), /* id, reduce: Node */ + + }, + }, + actionRow{ // S45 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(51), /* {, reduce: OptPort */ + reduce(51), /* }, reduce: OptPort */ + nil, /* empty */ + nil, /* strict */ + reduce(51), /* graphx, reduce: OptPort */ + nil, /* digraph */ + reduce(51), /* ;, reduce: OptPort */ + reduce(51), /* --, reduce: OptPort */ + reduce(51), /* ->, reduce: OptPort */ + reduce(51), /* node, reduce: OptPort */ + reduce(51), /* edge, reduce: OptPort */ + reduce(51), /* [, reduce: OptPort */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(51), /* subgraph, reduce: OptPort */ + nil, /* : */ + reduce(51), /* id, reduce: OptPort */ + + }, + }, + actionRow{ // S46 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + shift(62), /* id */ + + }, + }, + actionRow{ // S47 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(43), /* {, reduce: OptSubgraphID */ + reduce(10), /* }, reduce: OptStmtList */ + nil, /* empty */ + nil, /* strict */ + shift(14), /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + shift(25), /* node */ + shift(26), /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + shift(29), /* subgraph */ + nil, /* : */ + shift(30), /* id */ + + }, + }, + actionRow{ // S48 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(44), /* {, reduce: OptSubgraphID */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S49 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(9), /* {, reduce: StmtList */ + reduce(9), /* }, reduce: StmtList */ + nil, /* empty */ + nil, /* strict */ + reduce(9), /* graphx, reduce: StmtList */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + reduce(9), /* node, reduce: StmtList */ + reduce(9), /* edge, reduce: StmtList */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(9), /* subgraph, reduce: StmtList */ + nil, /* : */ + reduce(9), /* id, reduce: StmtList */ + + }, + }, + actionRow{ // S50 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + reduce(36), /* ], reduce: OptAList */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + shift(55), /* id */ + + }, + }, + actionRow{ // S51 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + shift(68), /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + reduce(38), /* ], reduce: OptSep */ + shift(70), /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + reduce(38), /* id, reduce: OptSep */ + + }, + }, + actionRow{ // S52 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + shift(71), /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S53 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + reduce(37), /* ], reduce: OptAList */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + shift(55), /* id */ + + }, + }, + actionRow{ // S54 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + shift(73), /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S55 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + reduce(52), /* =, reduce: ID */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S56 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(20), /* {, reduce: EdgeStmt */ + reduce(20), /* }, reduce: EdgeStmt */ + nil, /* empty */ + nil, /* strict */ + reduce(20), /* graphx, reduce: EdgeStmt */ + nil, /* digraph */ + reduce(20), /* ;, reduce: EdgeStmt */ + nil, /* -- */ + nil, /* -> */ + reduce(20), /* node, reduce: EdgeStmt */ + reduce(20), /* edge, reduce: EdgeStmt */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(20), /* subgraph, reduce: EdgeStmt */ + nil, /* : */ + reduce(20), /* id, reduce: EdgeStmt */ + + }, + }, + actionRow{ // S57 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(46), /* {, reduce: Vertex */ + reduce(46), /* }, reduce: Vertex */ + nil, /* empty */ + nil, /* strict */ + reduce(46), /* graphx, reduce: Vertex */ + nil, /* digraph */ + reduce(46), /* ;, reduce: Vertex */ + reduce(46), /* --, reduce: Vertex */ + reduce(46), /* ->, reduce: Vertex */ + reduce(46), /* node, reduce: Vertex */ + reduce(46), /* edge, reduce: Vertex */ + reduce(46), /* [, reduce: Vertex */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(46), /* subgraph, reduce: Vertex */ + nil, /* : */ + reduce(46), /* id, reduce: Vertex */ + + }, + }, + actionRow{ // S58 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(45), /* {, reduce: Vertex */ + reduce(45), /* }, reduce: Vertex */ + nil, /* empty */ + nil, /* strict */ + reduce(45), /* graphx, reduce: Vertex */ + nil, /* digraph */ + reduce(45), /* ;, reduce: Vertex */ + reduce(45), /* --, reduce: Vertex */ + reduce(45), /* ->, reduce: Vertex */ + reduce(45), /* node, reduce: Vertex */ + reduce(45), /* edge, reduce: Vertex */ + reduce(45), /* [, reduce: Vertex */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(45), /* subgraph, reduce: Vertex */ + nil, /* : */ + reduce(45), /* id, reduce: Vertex */ + + }, + }, + actionRow{ // S59 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(24), /* {, reduce: OptEdge */ + reduce(24), /* }, reduce: OptEdge */ + nil, /* empty */ + nil, /* strict */ + reduce(24), /* graphx, reduce: OptEdge */ + nil, /* digraph */ + reduce(24), /* ;, reduce: OptEdge */ + shift(40), /* -- */ + shift(41), /* -> */ + reduce(24), /* node, reduce: OptEdge */ + reduce(24), /* edge, reduce: OptEdge */ + reduce(24), /* [, reduce: OptEdge */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(24), /* subgraph, reduce: OptEdge */ + nil, /* : */ + reduce(24), /* id, reduce: OptEdge */ + + }, + }, + actionRow{ // S60 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(50), /* {, reduce: OptPort */ + reduce(50), /* }, reduce: OptPort */ + nil, /* empty */ + nil, /* strict */ + reduce(50), /* graphx, reduce: OptPort */ + nil, /* digraph */ + reduce(50), /* ;, reduce: OptPort */ + reduce(50), /* --, reduce: OptPort */ + reduce(50), /* ->, reduce: OptPort */ + reduce(50), /* node, reduce: OptPort */ + reduce(50), /* edge, reduce: OptPort */ + reduce(50), /* [, reduce: OptPort */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(50), /* subgraph, reduce: OptPort */ + shift(46), /* : */ + reduce(50), /* id, reduce: OptPort */ + + }, + }, + actionRow{ // S61 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + shift(76), /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S62 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(52), /* {, reduce: ID */ + reduce(52), /* }, reduce: ID */ + nil, /* empty */ + nil, /* strict */ + reduce(52), /* graphx, reduce: ID */ + nil, /* digraph */ + reduce(52), /* ;, reduce: ID */ + reduce(52), /* --, reduce: ID */ + reduce(52), /* ->, reduce: ID */ + reduce(52), /* node, reduce: ID */ + reduce(52), /* edge, reduce: ID */ + reduce(52), /* [, reduce: ID */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(52), /* subgraph, reduce: ID */ + reduce(52), /* :, reduce: ID */ + reduce(52), /* id, reduce: ID */ + + }, + }, + actionRow{ // S63 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(41), /* {, reduce: Attr */ + reduce(41), /* }, reduce: Attr */ + nil, /* empty */ + nil, /* strict */ + reduce(41), /* graphx, reduce: Attr */ + nil, /* digraph */ + reduce(41), /* ;, reduce: Attr */ + nil, /* -- */ + nil, /* -> */ + reduce(41), /* node, reduce: Attr */ + reduce(41), /* edge, reduce: Attr */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(41), /* subgraph, reduce: Attr */ + nil, /* : */ + reduce(41), /* id, reduce: Attr */ + + }, + }, + actionRow{ // S64 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(52), /* {, reduce: ID */ + reduce(52), /* }, reduce: ID */ + nil, /* empty */ + nil, /* strict */ + reduce(52), /* graphx, reduce: ID */ + nil, /* digraph */ + reduce(52), /* ;, reduce: ID */ + nil, /* -- */ + nil, /* -> */ + reduce(52), /* node, reduce: ID */ + reduce(52), /* edge, reduce: ID */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(52), /* subgraph, reduce: ID */ + nil, /* : */ + reduce(52), /* id, reduce: ID */ + + }, + }, + actionRow{ // S65 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(48), /* {, reduce: Port */ + reduce(48), /* }, reduce: Port */ + nil, /* empty */ + nil, /* strict */ + reduce(48), /* graphx, reduce: Port */ + nil, /* digraph */ + reduce(48), /* ;, reduce: Port */ + reduce(48), /* --, reduce: Port */ + reduce(48), /* ->, reduce: Port */ + reduce(48), /* node, reduce: Port */ + reduce(48), /* edge, reduce: Port */ + reduce(48), /* [, reduce: Port */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(48), /* subgraph, reduce: Port */ + shift(77), /* : */ + reduce(48), /* id, reduce: Port */ + + }, + }, + actionRow{ // S66 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + shift(78), /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S67 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + shift(79), /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S68 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + reduce(39), /* ], reduce: OptSep */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + reduce(39), /* id, reduce: OptSep */ + + }, + }, + actionRow{ // S69 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + reduce(34), /* ], reduce: AList */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + reduce(34), /* id, reduce: AList */ + + }, + }, + actionRow{ // S70 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + reduce(40), /* ], reduce: OptSep */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + reduce(40), /* id, reduce: OptSep */ + + }, + }, + actionRow{ // S71 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(30), /* {, reduce: AttrList */ + reduce(30), /* }, reduce: AttrList */ + nil, /* empty */ + nil, /* strict */ + reduce(30), /* graphx, reduce: AttrList */ + nil, /* digraph */ + reduce(30), /* ;, reduce: AttrList */ + nil, /* -- */ + nil, /* -> */ + reduce(30), /* node, reduce: AttrList */ + reduce(30), /* edge, reduce: AttrList */ + reduce(30), /* [, reduce: AttrList */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(30), /* subgraph, reduce: AttrList */ + nil, /* : */ + reduce(30), /* id, reduce: AttrList */ + + }, + }, + actionRow{ // S72 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + shift(68), /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + reduce(38), /* ], reduce: OptSep */ + shift(70), /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + reduce(38), /* id, reduce: OptSep */ + + }, + }, + actionRow{ // S73 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + shift(82), /* id */ + + }, + }, + actionRow{ // S74 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(25), /* {, reduce: OptEdge */ + reduce(25), /* }, reduce: OptEdge */ + nil, /* empty */ + nil, /* strict */ + reduce(25), /* graphx, reduce: OptEdge */ + nil, /* digraph */ + reduce(25), /* ;, reduce: OptEdge */ + nil, /* -- */ + nil, /* -> */ + reduce(25), /* node, reduce: OptEdge */ + reduce(25), /* edge, reduce: OptEdge */ + reduce(25), /* [, reduce: OptEdge */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(25), /* subgraph, reduce: OptEdge */ + nil, /* : */ + reduce(25), /* id, reduce: OptEdge */ + + }, + }, + actionRow{ // S75 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(21), /* {, reduce: Edge */ + reduce(21), /* }, reduce: Edge */ + nil, /* empty */ + nil, /* strict */ + reduce(21), /* graphx, reduce: Edge */ + nil, /* digraph */ + reduce(21), /* ;, reduce: Edge */ + nil, /* -- */ + nil, /* -> */ + reduce(21), /* node, reduce: Edge */ + reduce(21), /* edge, reduce: Edge */ + reduce(21), /* [, reduce: Edge */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(21), /* subgraph, reduce: Edge */ + nil, /* : */ + reduce(21), /* id, reduce: Edge */ + + }, + }, + actionRow{ // S76 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(43), /* {, reduce: OptSubgraphID */ + reduce(10), /* }, reduce: OptStmtList */ + nil, /* empty */ + nil, /* strict */ + shift(14), /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + shift(25), /* node */ + shift(26), /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + shift(29), /* subgraph */ + nil, /* : */ + shift(30), /* id */ + + }, + }, + actionRow{ // S77 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + shift(85), /* id */ + + }, + }, + actionRow{ // S78 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(42), /* {, reduce: Subgraph */ + reduce(42), /* }, reduce: Subgraph */ + nil, /* empty */ + nil, /* strict */ + reduce(42), /* graphx, reduce: Subgraph */ + nil, /* digraph */ + reduce(42), /* ;, reduce: Subgraph */ + reduce(42), /* --, reduce: Subgraph */ + reduce(42), /* ->, reduce: Subgraph */ + reduce(42), /* node, reduce: Subgraph */ + reduce(42), /* edge, reduce: Subgraph */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(42), /* subgraph, reduce: Subgraph */ + nil, /* : */ + reduce(42), /* id, reduce: Subgraph */ + + }, + }, + actionRow{ // S79 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(31), /* {, reduce: AttrList */ + reduce(31), /* }, reduce: AttrList */ + nil, /* empty */ + nil, /* strict */ + reduce(31), /* graphx, reduce: AttrList */ + nil, /* digraph */ + reduce(31), /* ;, reduce: AttrList */ + nil, /* -- */ + nil, /* -> */ + reduce(31), /* node, reduce: AttrList */ + reduce(31), /* edge, reduce: AttrList */ + reduce(31), /* [, reduce: AttrList */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(31), /* subgraph, reduce: AttrList */ + nil, /* : */ + reduce(31), /* id, reduce: AttrList */ + + }, + }, + actionRow{ // S80 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + reduce(35), /* ], reduce: AList */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + reduce(35), /* id, reduce: AList */ + + }, + }, + actionRow{ // S81 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + reduce(41), /* ;, reduce: Attr */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + reduce(41), /* ], reduce: Attr */ + reduce(41), /* ,, reduce: Attr */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + reduce(41), /* id, reduce: Attr */ + + }, + }, + actionRow{ // S82 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + nil, /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + reduce(52), /* ;, reduce: ID */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + reduce(52), /* ], reduce: ID */ + reduce(52), /* ,, reduce: ID */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + reduce(52), /* id, reduce: ID */ + + }, + }, + actionRow{ // S83 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + nil, /* { */ + shift(86), /* } */ + nil, /* empty */ + nil, /* strict */ + nil, /* graphx */ + nil, /* digraph */ + nil, /* ; */ + nil, /* -- */ + nil, /* -> */ + nil, /* node */ + nil, /* edge */ + nil, /* [ */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + nil, /* subgraph */ + nil, /* : */ + nil, /* id */ + + }, + }, + actionRow{ // S84 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(49), /* {, reduce: Port */ + reduce(49), /* }, reduce: Port */ + nil, /* empty */ + nil, /* strict */ + reduce(49), /* graphx, reduce: Port */ + nil, /* digraph */ + reduce(49), /* ;, reduce: Port */ + reduce(49), /* --, reduce: Port */ + reduce(49), /* ->, reduce: Port */ + reduce(49), /* node, reduce: Port */ + reduce(49), /* edge, reduce: Port */ + reduce(49), /* [, reduce: Port */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(49), /* subgraph, reduce: Port */ + nil, /* : */ + reduce(49), /* id, reduce: Port */ + + }, + }, + actionRow{ // S85 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(52), /* {, reduce: ID */ + reduce(52), /* }, reduce: ID */ + nil, /* empty */ + nil, /* strict */ + reduce(52), /* graphx, reduce: ID */ + nil, /* digraph */ + reduce(52), /* ;, reduce: ID */ + reduce(52), /* --, reduce: ID */ + reduce(52), /* ->, reduce: ID */ + reduce(52), /* node, reduce: ID */ + reduce(52), /* edge, reduce: ID */ + reduce(52), /* [, reduce: ID */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(52), /* subgraph, reduce: ID */ + nil, /* : */ + reduce(52), /* id, reduce: ID */ + + }, + }, + actionRow{ // S86 + canRecover: false, + actions: [numSymbols]action{ + nil, /* INVALID */ + nil, /* $ */ + reduce(42), /* {, reduce: Subgraph */ + reduce(42), /* }, reduce: Subgraph */ + nil, /* empty */ + nil, /* strict */ + reduce(42), /* graphx, reduce: Subgraph */ + nil, /* digraph */ + reduce(42), /* ;, reduce: Subgraph */ + reduce(42), /* --, reduce: Subgraph */ + reduce(42), /* ->, reduce: Subgraph */ + reduce(42), /* node, reduce: Subgraph */ + reduce(42), /* edge, reduce: Subgraph */ + reduce(42), /* [, reduce: Subgraph */ + nil, /* ] */ + nil, /* , */ + nil, /* = */ + reduce(42), /* subgraph, reduce: Subgraph */ + nil, /* : */ + reduce(42), /* id, reduce: Subgraph */ + + }, + }, +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/parser/gototable.go b/vendor/github.com/gonum/graph/formats/dot/internal/parser/gototable.go new file mode 100644 index 000000000000..b45395760a21 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/parser/gototable.go @@ -0,0 +1,2894 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package parser + +const numNTSymbols = 30 + +type ( + gotoTable [numStates]gotoRow + gotoRow [numNTSymbols]int +) + +var gotoTab = gotoTable{ + gotoRow{ // S0 + -1, // S' + 1, // File + 2, // Graph + 3, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S1 + -1, // S' + -1, // File + 5, // Graph + 3, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S2 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S3 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + 6, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S4 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S5 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S6 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + 10, // ID + 9, // OptID + + }, + gotoRow{ // S7 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S8 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S9 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S10 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S11 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S12 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + 15, // StmtList + 13, // OptStmtList + 16, // Stmt + -1, // OptSemi + 17, // NodeStmt + 18, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + 19, // AttrStmt + 24, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + 20, // Attr + 21, // Subgraph + 28, // OptSubgraphID + 23, // Vertex + 22, // Node + -1, // Port + -1, // OptPort + 27, // ID + -1, // OptID + + }, + gotoRow{ // S13 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S14 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S15 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + 32, // Stmt + -1, // OptSemi + 17, // NodeStmt + 18, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + 19, // AttrStmt + 24, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + 20, // Attr + 21, // Subgraph + 28, // OptSubgraphID + 23, // Vertex + 22, // Node + -1, // Port + -1, // OptPort + 27, // ID + -1, // OptID + + }, + gotoRow{ // S16 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + 33, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S17 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S18 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S19 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S20 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S21 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S22 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + 36, // AttrList + 35, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S23 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + 38, // Edge + 39, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S24 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + 42, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S25 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S26 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S27 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + 45, // Port + 44, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S28 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S29 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + 10, // ID + 48, // OptID + + }, + gotoRow{ // S30 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S31 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S32 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + 49, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S33 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S34 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S35 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S36 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S37 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + 53, // AList + 52, // OptAList + -1, // OptSep + 51, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + 54, // ID + -1, // OptID + + }, + gotoRow{ // S38 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + 36, // AttrList + 56, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S39 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + 57, // Subgraph + 61, // OptSubgraphID + 59, // Vertex + 58, // Node + -1, // Port + -1, // OptPort + 60, // ID + -1, // OptID + + }, + gotoRow{ // S40 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S41 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S42 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S43 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + 63, // ID + -1, // OptID + + }, + gotoRow{ // S44 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S45 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S46 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + 65, // ID + -1, // OptID + + }, + gotoRow{ // S47 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + 15, // StmtList + 66, // OptStmtList + 16, // Stmt + -1, // OptSemi + 17, // NodeStmt + 18, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + 19, // AttrStmt + 24, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + 20, // Attr + 21, // Subgraph + 28, // OptSubgraphID + 23, // Vertex + 22, // Node + -1, // Port + -1, // OptPort + 27, // ID + -1, // OptID + + }, + gotoRow{ // S48 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S49 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S50 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + 53, // AList + 67, // OptAList + -1, // OptSep + 51, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + 54, // ID + -1, // OptID + + }, + gotoRow{ // S51 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + 69, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S52 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S53 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + 72, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + 54, // ID + -1, // OptID + + }, + gotoRow{ // S54 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S55 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S56 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S57 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S58 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S59 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + 74, // Edge + 39, // DirectedEdge + 75, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S60 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + 45, // Port + 44, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S61 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S62 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S63 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S64 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S65 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S66 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S67 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S68 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S69 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S70 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S71 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S72 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + 80, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S73 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + 81, // ID + -1, // OptID + + }, + gotoRow{ // S74 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S75 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S76 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + 15, // StmtList + 83, // OptStmtList + 16, // Stmt + -1, // OptSemi + 17, // NodeStmt + 18, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + 19, // AttrStmt + 24, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + 20, // Attr + 21, // Subgraph + 28, // OptSubgraphID + 23, // Vertex + 22, // Node + -1, // Port + -1, // OptPort + 27, // ID + -1, // OptID + + }, + gotoRow{ // S77 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + 84, // ID + -1, // OptID + + }, + gotoRow{ // S78 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S79 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S80 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S81 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S82 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S83 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S84 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S85 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, + gotoRow{ // S86 + -1, // S' + -1, // File + -1, // Graph + -1, // OptStrict + -1, // DirectedGraph + -1, // StmtList + -1, // OptStmtList + -1, // Stmt + -1, // OptSemi + -1, // NodeStmt + -1, // EdgeStmt + -1, // Edge + -1, // DirectedEdge + -1, // OptEdge + -1, // AttrStmt + -1, // Component + -1, // AttrList + -1, // OptAttrList + -1, // AList + -1, // OptAList + -1, // OptSep + -1, // Attr + -1, // Subgraph + -1, // OptSubgraphID + -1, // Vertex + -1, // Node + -1, // Port + -1, // OptPort + -1, // ID + -1, // OptID + + }, +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/parser/parser.go b/vendor/github.com/gonum/graph/formats/dot/internal/parser/parser.go new file mode 100644 index 000000000000..b63555cf7e6a --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/parser/parser.go @@ -0,0 +1,222 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package parser + +import ( + "bytes" + "fmt" + + parseError "github.com/gonum/graph/formats/dot/internal/errors" + "github.com/gonum/graph/formats/dot/internal/token" +) + +const ( + numProductions = 55 + numStates = 87 + numSymbols = 50 +) + +// Stack + +type stack struct { + state []int + attrib []Attrib +} + +const iNITIAL_STACK_SIZE = 100 + +func newStack() *stack { + return &stack{state: make([]int, 0, iNITIAL_STACK_SIZE), + attrib: make([]Attrib, 0, iNITIAL_STACK_SIZE), + } +} + +func (this *stack) reset() { + this.state = this.state[0:0] + this.attrib = this.attrib[0:0] +} + +func (this *stack) push(s int, a Attrib) { + this.state = append(this.state, s) + this.attrib = append(this.attrib, a) +} + +func (this *stack) top() int { + return this.state[len(this.state)-1] +} + +func (this *stack) peek(pos int) int { + return this.state[pos] +} + +func (this *stack) topIndex() int { + return len(this.state) - 1 +} + +func (this *stack) popN(items int) []Attrib { + lo, hi := len(this.state)-items, len(this.state) + + attrib := this.attrib[lo:hi] + + this.state = this.state[:lo] + this.attrib = this.attrib[:lo] + + return attrib +} + +func (S *stack) String() string { + w := new(bytes.Buffer) + fmt.Fprintf(w, "stack:\n") + for i, st := range S.state { + fmt.Fprintf(w, "\t%d:%d , ", i, st) + if S.attrib[i] == nil { + fmt.Fprintf(w, "nil") + } else { + fmt.Fprintf(w, "%v", S.attrib[i]) + } + fmt.Fprintf(w, "\n") + } + return w.String() +} + +// Parser + +type Parser struct { + stack *stack + nextToken *token.Token + pos int +} + +type Scanner interface { + Scan() (tok *token.Token) +} + +func NewParser() *Parser { + p := &Parser{stack: newStack()} + p.Reset() + return p +} + +func (P *Parser) Reset() { + P.stack.reset() + P.stack.push(0, nil) +} + +func (P *Parser) Error(err error, scanner Scanner) (recovered bool, errorAttrib *parseError.Error) { + errorAttrib = &parseError.Error{ + Err: err, + ErrorToken: P.nextToken, + ErrorSymbols: P.popNonRecoveryStates(), + ExpectedTokens: make([]string, 0, 8), + } + for t, action := range actionTab[P.stack.top()].actions { + if action != nil { + errorAttrib.ExpectedTokens = append(errorAttrib.ExpectedTokens, token.TokMap.Id(token.Type(t))) + } + } + + if action := actionTab[P.stack.top()].actions[token.TokMap.Type("error")]; action != nil { + P.stack.push(int(action.(shift)), errorAttrib) // action can only be shift + } else { + return + } + + if action := actionTab[P.stack.top()].actions[P.nextToken.Type]; action != nil { + recovered = true + } + for !recovered && P.nextToken.Type != token.EOF { + P.nextToken = scanner.Scan() + if action := actionTab[P.stack.top()].actions[P.nextToken.Type]; action != nil { + recovered = true + } + } + + return +} + +func (P *Parser) popNonRecoveryStates() (removedAttribs []parseError.ErrorSymbol) { + if rs, ok := P.firstRecoveryState(); ok { + errorSymbols := P.stack.popN(int(P.stack.topIndex() - rs)) + removedAttribs = make([]parseError.ErrorSymbol, len(errorSymbols)) + for i, e := range errorSymbols { + removedAttribs[i] = e + } + } else { + removedAttribs = []parseError.ErrorSymbol{} + } + return +} + +// recoveryState points to the highest state on the stack, which can recover +func (P *Parser) firstRecoveryState() (recoveryState int, canRecover bool) { + recoveryState, canRecover = P.stack.topIndex(), actionTab[P.stack.top()].canRecover + for recoveryState > 0 && !canRecover { + recoveryState-- + canRecover = actionTab[P.stack.peek(recoveryState)].canRecover + } + return +} + +func (P *Parser) newError(err error) error { + e := &parseError.Error{ + Err: err, + StackTop: P.stack.top(), + ErrorToken: P.nextToken, + } + actRow := actionTab[P.stack.top()] + for i, t := range actRow.actions { + if t != nil { + e.ExpectedTokens = append(e.ExpectedTokens, token.TokMap.Id(token.Type(i))) + } + } + return e +} + +func (this *Parser) Parse(scanner Scanner) (res interface{}, err error) { + this.Reset() + this.nextToken = scanner.Scan() + for acc := false; !acc; { + action := actionTab[this.stack.top()].actions[this.nextToken.Type] + if action == nil { + if recovered, errAttrib := this.Error(nil, scanner); !recovered { + this.nextToken = errAttrib.ErrorToken + return nil, this.newError(nil) + } + if action = actionTab[this.stack.top()].actions[this.nextToken.Type]; action == nil { + panic("Error recovery led to invalid action") + } + } + + // fmt.Printf("S%d %s %s\n", this.stack.top(), token.TokMap.TokenString(this.nextToken), action.String()) + + switch act := action.(type) { + case accept: + res = this.stack.popN(1)[0] + acc = true + case shift: + this.stack.push(int(act), this.nextToken) + this.nextToken = scanner.Scan() + case reduce: + prod := productionsTable[int(act)] + attrib, err := prod.ReduceFunc(this.stack.popN(prod.NumSymbols)) + if err != nil { + return nil, this.newError(err) + } else { + this.stack.push(gotoTab[this.stack.top()][prod.NTType], attrib) + } + default: + panic("unknown action: " + action.String()) + } + } + return res, nil +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/parser/parser_test.go b/vendor/github.com/gonum/graph/formats/dot/internal/parser/parser_test.go new file mode 100644 index 000000000000..6b37df766eb5 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/parser/parser_test.go @@ -0,0 +1,114 @@ +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package parser_test + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/gonum/graph/formats/dot" +) + +func TestParseFile(t *testing.T) { + golden := []struct { + in string + out string + }{ + {in: "../testdata/empty.dot"}, + {in: "../testdata/graph.dot"}, + {in: "../testdata/digraph.dot"}, + {in: "../testdata/strict.dot"}, + {in: "../testdata/multi.dot"}, + {in: "../testdata/named_graph.dot"}, + {in: "../testdata/node_stmt.dot"}, + {in: "../testdata/edge_stmt.dot"}, + {in: "../testdata/attr_stmt.dot"}, + {in: "../testdata/attr.dot"}, + { + in: "../testdata/subgraph.dot", + out: "../testdata/subgraph.golden", + }, + { + in: "../testdata/semi.dot", + out: "../testdata/semi.golden", + }, + { + in: "../testdata/empty_attr.dot", + out: "../testdata/empty_attr.golden", + }, + { + in: "../testdata/attr_lists.dot", + out: "../testdata/attr_lists.golden", + }, + { + in: "../testdata/attr_sep.dot", + out: "../testdata/attr_sep.golden", + }, + {in: "../testdata/subgraph_vertex.dot"}, + { + in: "../testdata/port.dot", + out: "../testdata/port.golden", + }, + {in: "../testdata/quoted_id.dot"}, + { + in: "../testdata/backslash_newline_id.dot", + out: "../testdata/backslash_newline_id.golden", + }, + } + for _, g := range golden { + file, err := dot.ParseFile(g.in) + if err != nil { + t.Errorf("%q: unable to parse file; %v", g.in, err) + continue + } + // If no output path is specified, the input is already golden. + out := g.in + if len(g.out) > 0 { + out = g.out + } + buf, err := ioutil.ReadFile(out) + if err != nil { + t.Errorf("%q: unable to read file; %v", g.in, err) + continue + } + got := file.String() + // Remove trailing newline. + want := string(bytes.TrimSpace(buf)) + if got != want { + t.Errorf("%q: graph mismatch; expected `%s`, got `%s`", g.in, want, got) + } + } +} + +func TestParseError(t *testing.T) { + golden := []struct { + path string + want string + }{ + { + path: "../testdata/error.dot", + want: `Error in S30: INVALID(0,~), Pos(offset=13, line=2, column=7), expected one of: { } graphx ; -- -> node edge [ = subgraph : id `, + }, + } + for _, g := range golden { + _, err := dot.ParseFile(g.path) + if err == nil { + t.Errorf("%q: expected error, got nil", g.path) + continue + } + got := err.Error() + if got != g.want { + t.Errorf("%q: error mismatch; expected `%v`, got `%v`", g.path, g.want, got) + continue + } + } +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/parser/productionstable.go b/vendor/github.com/gonum/graph/formats/dot/internal/parser/productionstable.go new file mode 100644 index 000000000000..5b05144cd1dd --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/parser/productionstable.go @@ -0,0 +1,586 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package parser + +import ( + "github.com/gonum/graph/formats/dot/ast" + "github.com/gonum/graph/formats/dot/internal/astx" +) + +type ( + //TODO: change type and variable names to be consistent with other tables + ProdTab [numProductions]ProdTabEntry + ProdTabEntry struct { + String string + Id string + NTType int + Index int + NumSymbols int + ReduceFunc func([]Attrib) (Attrib, error) + } + Attrib interface { + } +) + +var productionsTable = ProdTab{ + ProdTabEntry{ + String: `S' : File << >>`, + Id: "S'", + NTType: 0, + Index: 0, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `File : Graph << astx.NewFile(X[0]) >>`, + Id: "File", + NTType: 1, + Index: 1, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewFile(X[0]) + }, + }, + ProdTabEntry{ + String: `File : File Graph << astx.AppendGraph(X[0], X[1]) >>`, + Id: "File", + NTType: 1, + Index: 2, + NumSymbols: 2, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.AppendGraph(X[0], X[1]) + }, + }, + ProdTabEntry{ + String: `Graph : OptStrict DirectedGraph OptID "{" OptStmtList "}" << astx.NewGraph(X[0], X[1], X[2], X[4]) >>`, + Id: "Graph", + NTType: 2, + Index: 3, + NumSymbols: 6, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewGraph(X[0], X[1], X[2], X[4]) + }, + }, + ProdTabEntry{ + String: `OptStrict : empty << false, nil >>`, + Id: "OptStrict", + NTType: 3, + Index: 4, + NumSymbols: 0, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return false, nil + }, + }, + ProdTabEntry{ + String: `OptStrict : strict << true, nil >>`, + Id: "OptStrict", + NTType: 3, + Index: 5, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return true, nil + }, + }, + ProdTabEntry{ + String: `DirectedGraph : graphx << false, nil >>`, + Id: "DirectedGraph", + NTType: 4, + Index: 6, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return false, nil + }, + }, + ProdTabEntry{ + String: `DirectedGraph : digraph << true, nil >>`, + Id: "DirectedGraph", + NTType: 4, + Index: 7, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return true, nil + }, + }, + ProdTabEntry{ + String: `StmtList : Stmt OptSemi << astx.NewStmtList(X[0]) >>`, + Id: "StmtList", + NTType: 5, + Index: 8, + NumSymbols: 2, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewStmtList(X[0]) + }, + }, + ProdTabEntry{ + String: `StmtList : StmtList Stmt OptSemi << astx.AppendStmt(X[0], X[1]) >>`, + Id: "StmtList", + NTType: 5, + Index: 9, + NumSymbols: 3, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.AppendStmt(X[0], X[1]) + }, + }, + ProdTabEntry{ + String: `OptStmtList : empty << >>`, + Id: "OptStmtList", + NTType: 6, + Index: 10, + NumSymbols: 0, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return nil, nil + }, + }, + ProdTabEntry{ + String: `OptStmtList : StmtList << >>`, + Id: "OptStmtList", + NTType: 6, + Index: 11, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `Stmt : NodeStmt << >>`, + Id: "Stmt", + NTType: 7, + Index: 12, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `Stmt : EdgeStmt << >>`, + Id: "Stmt", + NTType: 7, + Index: 13, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `Stmt : AttrStmt << >>`, + Id: "Stmt", + NTType: 7, + Index: 14, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `Stmt : Attr << >>`, + Id: "Stmt", + NTType: 7, + Index: 15, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `Stmt : Subgraph << >>`, + Id: "Stmt", + NTType: 7, + Index: 16, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `OptSemi : empty << >>`, + Id: "OptSemi", + NTType: 8, + Index: 17, + NumSymbols: 0, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return nil, nil + }, + }, + ProdTabEntry{ + String: `OptSemi : ";" << >>`, + Id: "OptSemi", + NTType: 8, + Index: 18, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `NodeStmt : Node OptAttrList << astx.NewNodeStmt(X[0], X[1]) >>`, + Id: "NodeStmt", + NTType: 9, + Index: 19, + NumSymbols: 2, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewNodeStmt(X[0], X[1]) + }, + }, + ProdTabEntry{ + String: `EdgeStmt : Vertex Edge OptAttrList << astx.NewEdgeStmt(X[0], X[1], X[2]) >>`, + Id: "EdgeStmt", + NTType: 10, + Index: 20, + NumSymbols: 3, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewEdgeStmt(X[0], X[1], X[2]) + }, + }, + ProdTabEntry{ + String: `Edge : DirectedEdge Vertex OptEdge << astx.NewEdge(X[0], X[1], X[2]) >>`, + Id: "Edge", + NTType: 11, + Index: 21, + NumSymbols: 3, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewEdge(X[0], X[1], X[2]) + }, + }, + ProdTabEntry{ + String: `DirectedEdge : "--" << false, nil >>`, + Id: "DirectedEdge", + NTType: 12, + Index: 22, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return false, nil + }, + }, + ProdTabEntry{ + String: `DirectedEdge : "->" << true, nil >>`, + Id: "DirectedEdge", + NTType: 12, + Index: 23, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return true, nil + }, + }, + ProdTabEntry{ + String: `OptEdge : empty << >>`, + Id: "OptEdge", + NTType: 13, + Index: 24, + NumSymbols: 0, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return nil, nil + }, + }, + ProdTabEntry{ + String: `OptEdge : Edge << >>`, + Id: "OptEdge", + NTType: 13, + Index: 25, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `AttrStmt : Component AttrList << astx.NewAttrStmt(X[0], X[1]) >>`, + Id: "AttrStmt", + NTType: 14, + Index: 26, + NumSymbols: 2, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewAttrStmt(X[0], X[1]) + }, + }, + ProdTabEntry{ + String: `Component : graphx << ast.KindGraph, nil >>`, + Id: "Component", + NTType: 15, + Index: 27, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return ast.KindGraph, nil + }, + }, + ProdTabEntry{ + String: `Component : node << ast.KindNode, nil >>`, + Id: "Component", + NTType: 15, + Index: 28, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return ast.KindNode, nil + }, + }, + ProdTabEntry{ + String: `Component : edge << ast.KindEdge, nil >>`, + Id: "Component", + NTType: 15, + Index: 29, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return ast.KindEdge, nil + }, + }, + ProdTabEntry{ + String: `AttrList : "[" OptAList "]" << X[1], nil >>`, + Id: "AttrList", + NTType: 16, + Index: 30, + NumSymbols: 3, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[1], nil + }, + }, + ProdTabEntry{ + String: `AttrList : AttrList "[" OptAList "]" << astx.AppendAttrList(X[0], X[2]) >>`, + Id: "AttrList", + NTType: 16, + Index: 31, + NumSymbols: 4, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.AppendAttrList(X[0], X[2]) + }, + }, + ProdTabEntry{ + String: `OptAttrList : empty << >>`, + Id: "OptAttrList", + NTType: 17, + Index: 32, + NumSymbols: 0, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return nil, nil + }, + }, + ProdTabEntry{ + String: `OptAttrList : AttrList << >>`, + Id: "OptAttrList", + NTType: 17, + Index: 33, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `AList : Attr OptSep << astx.NewAttrList(X[0]) >>`, + Id: "AList", + NTType: 18, + Index: 34, + NumSymbols: 2, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewAttrList(X[0]) + }, + }, + ProdTabEntry{ + String: `AList : AList Attr OptSep << astx.AppendAttr(X[0], X[1]) >>`, + Id: "AList", + NTType: 18, + Index: 35, + NumSymbols: 3, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.AppendAttr(X[0], X[1]) + }, + }, + ProdTabEntry{ + String: `OptAList : empty << >>`, + Id: "OptAList", + NTType: 19, + Index: 36, + NumSymbols: 0, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return nil, nil + }, + }, + ProdTabEntry{ + String: `OptAList : AList << >>`, + Id: "OptAList", + NTType: 19, + Index: 37, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `OptSep : empty << >>`, + Id: "OptSep", + NTType: 20, + Index: 38, + NumSymbols: 0, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return nil, nil + }, + }, + ProdTabEntry{ + String: `OptSep : ";" << >>`, + Id: "OptSep", + NTType: 20, + Index: 39, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `OptSep : "," << >>`, + Id: "OptSep", + NTType: 20, + Index: 40, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `Attr : ID "=" ID << astx.NewAttr(X[0], X[2]) >>`, + Id: "Attr", + NTType: 21, + Index: 41, + NumSymbols: 3, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewAttr(X[0], X[2]) + }, + }, + ProdTabEntry{ + String: `Subgraph : OptSubgraphID "{" OptStmtList "}" << astx.NewSubgraph(X[0], X[2]) >>`, + Id: "Subgraph", + NTType: 22, + Index: 42, + NumSymbols: 4, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewSubgraph(X[0], X[2]) + }, + }, + ProdTabEntry{ + String: `OptSubgraphID : empty << >>`, + Id: "OptSubgraphID", + NTType: 23, + Index: 43, + NumSymbols: 0, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return nil, nil + }, + }, + ProdTabEntry{ + String: `OptSubgraphID : subgraph OptID << X[1], nil >>`, + Id: "OptSubgraphID", + NTType: 23, + Index: 44, + NumSymbols: 2, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[1], nil + }, + }, + ProdTabEntry{ + String: `Vertex : Node << >>`, + Id: "Vertex", + NTType: 24, + Index: 45, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `Vertex : Subgraph << >>`, + Id: "Vertex", + NTType: 24, + Index: 46, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `Node : ID OptPort << astx.NewNode(X[0], X[1]) >>`, + Id: "Node", + NTType: 25, + Index: 47, + NumSymbols: 2, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewNode(X[0], X[1]) + }, + }, + ProdTabEntry{ + String: `Port : ":" ID << astx.NewPort(X[1], nil) >>`, + Id: "Port", + NTType: 26, + Index: 48, + NumSymbols: 2, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewPort(X[1], nil) + }, + }, + ProdTabEntry{ + String: `Port : ":" ID ":" ID << astx.NewPort(X[1], X[3]) >>`, + Id: "Port", + NTType: 26, + Index: 49, + NumSymbols: 4, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewPort(X[1], X[3]) + }, + }, + ProdTabEntry{ + String: `OptPort : empty << >>`, + Id: "OptPort", + NTType: 27, + Index: 50, + NumSymbols: 0, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return nil, nil + }, + }, + ProdTabEntry{ + String: `OptPort : Port << >>`, + Id: "OptPort", + NTType: 27, + Index: 51, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, + ProdTabEntry{ + String: `ID : id << astx.NewID(X[0]) >>`, + Id: "ID", + NTType: 28, + Index: 52, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return astx.NewID(X[0]) + }, + }, + ProdTabEntry{ + String: `OptID : empty << "", nil >>`, + Id: "OptID", + NTType: 29, + Index: 53, + NumSymbols: 0, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return "", nil + }, + }, + ProdTabEntry{ + String: `OptID : ID << >>`, + Id: "OptID", + NTType: 29, + Index: 54, + NumSymbols: 1, + ReduceFunc: func(X []Attrib) (Attrib, error) { + return X[0], nil + }, + }, +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/paste_copyright.bash b/vendor/github.com/gonum/graph/formats/dot/internal/paste_copyright.bash new file mode 100755 index 000000000000..83319810e8b2 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/paste_copyright.bash @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +find . -type f -name '*.go' \ +| xargs sed -i -e "s|// Code generated by gocc; DO NOT EDIT.|\ +// Code generated by gocc; DO NOT EDIT.\n\ +\n\ +// This file is dual licensed under CC0 and The gonum license.\n\ +//\n\ +// Copyright ©2017 The gonum Authors. All rights reserved.\n\ +// Use of this source code is governed by a BSD-style\n\ +// license that can be found in the LICENSE file.\n\ +//\n\ +// Copyright ©2017 Robin Eklind.\n\ +// This file is made available under a Creative Commons CC0 1.0\n\ +// Universal Public Domain Dedication.\n\ +|" + diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr.dot new file mode 100644 index 000000000000..dabe9734d897 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr.dot @@ -0,0 +1,4 @@ +digraph { + bgcolor=transparent + A +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_lists.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_lists.dot new file mode 100644 index 000000000000..b71411cdadfe --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_lists.dot @@ -0,0 +1,3 @@ +digraph { + A [style=filled] [fillcolor=red] +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_lists.golden b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_lists.golden new file mode 100644 index 000000000000..14316d935320 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_lists.golden @@ -0,0 +1,3 @@ +digraph { + A [style=filled fillcolor=red] +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_sep.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_sep.dot new file mode 100644 index 000000000000..ef47a1204130 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_sep.dot @@ -0,0 +1,3 @@ +digraph { + A [style=filled, fillcolor=red; color=blue] +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_sep.golden b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_sep.golden new file mode 100644 index 000000000000..95bfd97247bc --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_sep.golden @@ -0,0 +1,3 @@ +digraph { + A [style=filled fillcolor=red color=blue] +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_stmt.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_stmt.dot new file mode 100644 index 000000000000..e87d70a4273c --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/attr_stmt.dot @@ -0,0 +1,6 @@ +digraph { + graph [bgcolor=transparent] + node [style=filled fillcolor=white] + edge [minlen=2] + A -> B +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/backslash_newline_id.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/backslash_newline_id.dot new file mode 100644 index 000000000000..810513841c28 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/backslash_newline_id.dot @@ -0,0 +1,4 @@ +digraph { + A [name="hello \ +world"] +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/backslash_newline_id.golden b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/backslash_newline_id.golden new file mode 100644 index 000000000000..449c3a3e9063 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/backslash_newline_id.golden @@ -0,0 +1,3 @@ +digraph { + A [name="hello world"] +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/digraph.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/digraph.dot new file mode 100644 index 000000000000..1063ebfa131a --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/digraph.dot @@ -0,0 +1,3 @@ +digraph { + A -> B +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/edge_stmt.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/edge_stmt.dot new file mode 100644 index 000000000000..019d23d7b4b9 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/edge_stmt.dot @@ -0,0 +1,4 @@ +digraph { + A -> B -> C + D -> E [color=red minlen=2] +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/empty.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/empty.dot new file mode 100644 index 000000000000..418b7bcad5e6 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/empty.dot @@ -0,0 +1,2 @@ +graph { +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/empty_attr.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/empty_attr.dot new file mode 100644 index 000000000000..b92d60e50423 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/empty_attr.dot @@ -0,0 +1,3 @@ +digraph { + A [] +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/empty_attr.golden b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/empty_attr.golden new file mode 100644 index 000000000000..369e44953a95 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/empty_attr.golden @@ -0,0 +1,3 @@ +digraph { + A +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/error.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/error.dot new file mode 100644 index 000000000000..ad77f61f0ce5 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/error.dot @@ -0,0 +1,3 @@ +digraph { + A ~ B +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/graph.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/graph.dot new file mode 100644 index 000000000000..0524c1444e74 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/graph.dot @@ -0,0 +1,3 @@ +graph { + A -- B +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/multi.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/multi.dot new file mode 100644 index 000000000000..219451885d98 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/multi.dot @@ -0,0 +1,6 @@ +digraph { + A -> B +} +digraph { + C -> D +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/named_graph.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/named_graph.dot new file mode 100644 index 000000000000..3fc680dab3c5 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/named_graph.dot @@ -0,0 +1,3 @@ +graph G { + A +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/node_stmt.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/node_stmt.dot new file mode 100644 index 000000000000..369e44953a95 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/node_stmt.dot @@ -0,0 +1,3 @@ +digraph { + A +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/port.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/port.dot new file mode 100644 index 000000000000..1d4bf415dc6d --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/port.dot @@ -0,0 +1,11 @@ +digraph { + A:ne -> B:sw + C:foo -> D:bar:se + E:_ -> F + G:n + H:e + I:s + J:w + K:nw + L:c +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/port.golden b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/port.golden new file mode 100644 index 000000000000..18ca1f30e6b7 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/port.golden @@ -0,0 +1,11 @@ +digraph { + A:ne -> B:sw + C:foo -> D:bar:se + E -> F + G:n + H:e + I:s + J:w + K:nw + L:c +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/quoted_id.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/quoted_id.dot new file mode 100644 index 000000000000..0c3b56521ec8 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/quoted_id.dot @@ -0,0 +1,3 @@ +digraph { + "A" -> "B" ["color"="red"] +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/semi.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/semi.dot new file mode 100644 index 000000000000..c8bcbf34939e --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/semi.dot @@ -0,0 +1,3 @@ +digraph { + A -> B; C +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/semi.golden b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/semi.golden new file mode 100644 index 000000000000..61bca2687f88 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/semi.golden @@ -0,0 +1,4 @@ +digraph { + A -> B + C +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/strict.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/strict.dot new file mode 100644 index 000000000000..bf222ad936ad --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/strict.dot @@ -0,0 +1,4 @@ +strict digraph { + A -> B + A -> B +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/subgraph.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/subgraph.dot new file mode 100644 index 000000000000..a03667dda5ce --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/subgraph.dot @@ -0,0 +1,5 @@ +digraph { + {A} + subgraph {B} + subgraph S {C} +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/subgraph.golden b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/subgraph.golden new file mode 100644 index 000000000000..0cd6fe7296e7 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/subgraph.golden @@ -0,0 +1,5 @@ +digraph { + {A} + {B} + subgraph S {C} +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/testdata/subgraph_vertex.dot b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/subgraph_vertex.dot new file mode 100644 index 000000000000..b414ad841ff1 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/testdata/subgraph_vertex.dot @@ -0,0 +1,3 @@ +digraph { + {A B} -> C +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/token/token.go b/vendor/github.com/gonum/graph/formats/dot/internal/token/token.go new file mode 100644 index 000000000000..31db60351e99 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/token/token.go @@ -0,0 +1,116 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package token + +import ( + "fmt" +) + +type Token struct { + Type + Lit []byte + Pos +} + +type Type int + +const ( + INVALID Type = iota + EOF +) + +type Pos struct { + Offset int + Line int + Column int +} + +func (this Pos) String() string { + return fmt.Sprintf("Pos(offset=%d, line=%d, column=%d)", this.Offset, this.Line, this.Column) +} + +type TokenMap struct { + typeMap []string + idMap map[string]Type +} + +func (this TokenMap) Id(tok Type) string { + if int(tok) < len(this.typeMap) { + return this.typeMap[tok] + } + return "unknown" +} + +func (this TokenMap) Type(tok string) Type { + if typ, exist := this.idMap[tok]; exist { + return typ + } + return INVALID +} + +func (this TokenMap) TokenString(tok *Token) string { + //TODO: refactor to print pos & token string properly + return fmt.Sprintf("%s(%d,%s)", this.Id(tok.Type), tok.Type, tok.Lit) +} + +func (this TokenMap) StringType(typ Type) string { + return fmt.Sprintf("%s(%d)", this.Id(typ), typ) +} + +var TokMap = TokenMap{ + typeMap: []string{ + "INVALID", + "$", + "{", + "}", + "empty", + "strict", + "graphx", + "digraph", + ";", + "--", + "->", + "node", + "edge", + "[", + "]", + ",", + "=", + "subgraph", + ":", + "id", + }, + + idMap: map[string]Type{ + "INVALID": 0, + "$": 1, + "{": 2, + "}": 3, + "empty": 4, + "strict": 5, + "graphx": 6, + "digraph": 7, + ";": 8, + "--": 9, + "->": 10, + "node": 11, + "edge": 12, + "[": 13, + "]": 14, + ",": 15, + "=": 16, + "subgraph": 17, + ":": 18, + "id": 19, + }, +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/util/litconv.go b/vendor/github.com/gonum/graph/formats/dot/internal/util/litconv.go new file mode 100644 index 000000000000..ed6e348ba3b6 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/util/litconv.go @@ -0,0 +1,118 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package util + +import ( + "fmt" + "strconv" + "unicode" + "unicode/utf8" +) + +/* Interface */ + +/* +Convert the literal value of a scanned token to rune +*/ +func RuneValue(lit []byte) rune { + if lit[1] == '\\' { + return escapeCharVal(lit) + } + r, size := utf8.DecodeRune(lit[1:]) + if size != len(lit)-2 { + panic(fmt.Sprintf("Error decoding rune. Lit: %s, rune: %d, size%d\n", lit, r, size)) + } + return r +} + +/* +Convert the literal value of a scanned token to int64 +*/ +func IntValue(lit []byte) (int64, error) { + return strconv.ParseInt(string(lit), 10, 64) +} + +/* +Convert the literal value of a scanned token to uint64 +*/ +func UintValue(lit []byte) (uint64, error) { + return strconv.ParseUint(string(lit), 10, 64) +} + +/* Util */ + +func escapeCharVal(lit []byte) rune { + var i, base, max uint32 + offset := 2 + switch lit[offset] { + case 'a': + return '\a' + case 'b': + return '\b' + case 'f': + return '\f' + case 'n': + return '\n' + case 'r': + return '\r' + case 't': + return '\t' + case 'v': + return '\v' + case '\\': + return '\\' + case '\'': + return '\'' + case '0', '1', '2', '3', '4', '5', '6', '7': + i, base, max = 3, 8, 255 + case 'x': + i, base, max = 2, 16, 255 + offset++ + case 'u': + i, base, max = 4, 16, unicode.MaxRune + offset++ + case 'U': + i, base, max = 8, 16, unicode.MaxRune + offset++ + default: + panic(fmt.Sprintf("Error decoding character literal: %s\n", lit)) + } + + var x uint32 + for ; i > 0 && offset < len(lit)-1; i-- { + ch, size := utf8.DecodeRune(lit[offset:]) + offset += size + d := uint32(digitVal(ch)) + if d >= base { + panic(fmt.Sprintf("charVal(%s): illegal character (%c) in escape sequence. size=%d, offset=%d", lit, ch, size, offset)) + } + x = x*base + d + } + if x > max || 0xD800 <= x && x < 0xE000 { + panic(fmt.Sprintf("Error decoding escape char value. Lit:%s, offset:%d, escape sequence is invalid Unicode code point\n", lit, offset)) + } + + return rune(x) +} + +func digitVal(ch rune) int { + switch { + case '0' <= ch && ch <= '9': + return int(ch) - '0' + case 'a' <= ch && ch <= 'f': + return int(ch) - 'a' + 10 + case 'A' <= ch && ch <= 'F': + return int(ch) - 'A' + 10 + } + return 16 // larger than any legal digit val +} diff --git a/vendor/github.com/gonum/graph/formats/dot/internal/util/rune.go b/vendor/github.com/gonum/graph/formats/dot/internal/util/rune.go new file mode 100644 index 000000000000..583b1e0678ef --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/internal/util/rune.go @@ -0,0 +1,49 @@ +// Code generated by gocc; DO NOT EDIT. + +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package util + +import ( + "fmt" +) + +func RuneToString(r rune) string { + if r >= 0x20 && r < 0x7f { + return fmt.Sprintf("'%c'", r) + } + switch r { + case 0x07: + return "'\\a'" + case 0x08: + return "'\\b'" + case 0x0C: + return "'\\f'" + case 0x0A: + return "'\\n'" + case 0x0D: + return "'\\r'" + case 0x09: + return "'\\t'" + case 0x0b: + return "'\\v'" + case 0x5c: + return "'\\\\\\'" + case 0x27: + return "'\\''" + case 0x22: + return "'\\\"'" + } + if r < 0x10000 { + return fmt.Sprintf("\\u%04x", r) + } + return fmt.Sprintf("\\U%08x", r) +} diff --git a/vendor/github.com/gonum/graph/formats/dot/sem.go b/vendor/github.com/gonum/graph/formats/dot/sem.go new file mode 100644 index 000000000000..d4643e033af0 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/sem.go @@ -0,0 +1,160 @@ +// This file is dual licensed under CC0 and The gonum license. +// +// Copyright ©2017 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. +// +// Copyright ©2017 Robin Eklind. +// This file is made available under a Creative Commons CC0 1.0 +// Universal Public Domain Dedication. + +package dot + +import ( + "fmt" + + "github.com/gonum/graph/formats/dot/ast" +) + +// check validates the semantics of the given DOT file. +func check(file *ast.File) error { + for _, graph := range file.Graphs { + // TODO: Check graph.ID for duplicates? + if err := checkGraph(graph); err != nil { + return err + } + } + return nil +} + +// check validates the semantics of the given graph. +func checkGraph(graph *ast.Graph) error { + for _, stmt := range graph.Stmts { + if err := checkStmt(graph, stmt); err != nil { + return err + } + } + return nil +} + +// check validates the semantics of the given statement. +func checkStmt(graph *ast.Graph, stmt ast.Stmt) error { + switch stmt := stmt.(type) { + case *ast.NodeStmt: + return checkNodeStmt(graph, stmt) + case *ast.EdgeStmt: + return checkEdgeStmt(graph, stmt) + case *ast.AttrStmt: + return checkAttrStmt(graph, stmt) + case *ast.Attr: + // TODO: Verify that the attribute is indeed of graph component kind. + return checkAttr(graph, ast.KindGraph, stmt) + case *ast.Subgraph: + return checkSubgraph(graph, stmt) + default: + panic(fmt.Sprintf("support for statement of type %T not yet implemented", stmt)) + } +} + +// checkNodeStmt validates the semantics of the given node statement. +func checkNodeStmt(graph *ast.Graph, stmt *ast.NodeStmt) error { + if err := checkNode(graph, stmt.Node); err != nil { + return err + } + for _, attr := range stmt.Attrs { + // TODO: Verify that the attribute is indeed of node component kind. + if err := checkAttr(graph, ast.KindNode, attr); err != nil { + return err + } + } + return nil +} + +// checkEdgeStmt validates the semantics of the given edge statement. +func checkEdgeStmt(graph *ast.Graph, stmt *ast.EdgeStmt) error { + // TODO: if graph.Strict, check for multi-edges. + if err := checkVertex(graph, stmt.From); err != nil { + return err + } + for _, attr := range stmt.Attrs { + // TODO: Verify that the attribute is indeed of edge component kind. + if err := checkAttr(graph, ast.KindEdge, attr); err != nil { + return err + } + } + return checkEdge(graph, stmt.From, stmt.To) +} + +// checkEdge validates the semantics of the given edge. +func checkEdge(graph *ast.Graph, from ast.Vertex, to *ast.Edge) error { + if !graph.Directed && to.Directed { + return fmt.Errorf("undirected graph %q contains directed edge from %q to %q", graph.ID, from, to.Vertex) + } + if err := checkVertex(graph, to.Vertex); err != nil { + return err + } + if to.To != nil { + return checkEdge(graph, to.Vertex, to.To) + } + return nil +} + +// checkAttrStmt validates the semantics of the given attribute statement. +func checkAttrStmt(graph *ast.Graph, stmt *ast.AttrStmt) error { + for _, attr := range stmt.Attrs { + if err := checkAttr(graph, stmt.Kind, attr); err != nil { + return err + } + } + return nil +} + +// checkAttr validates the semantics of the given attribute for the given +// component kind. +func checkAttr(graph *ast.Graph, kind ast.Kind, attr *ast.Attr) error { + switch kind { + case ast.KindGraph: + // TODO: Validate key-value pairs for graphs. + return nil + case ast.KindNode: + // TODO: Validate key-value pairs for nodes. + return nil + case ast.KindEdge: + // TODO: Validate key-value pairs for edges. + return nil + default: + panic(fmt.Sprintf("support for component kind %v not yet supported", kind)) + } +} + +// checkSubgraph validates the semantics of the given subgraph. +func checkSubgraph(graph *ast.Graph, subgraph *ast.Subgraph) error { + // TODO: Check subgraph.ID for duplicates? + for _, stmt := range subgraph.Stmts { + // TODO: Refine handling of subgraph statements? + // checkSubgraphStmt(graph, subgraph, stmt) + if err := checkStmt(graph, stmt); err != nil { + return err + } + } + return nil +} + +// checkVertex validates the semantics of the given vertex. +func checkVertex(graph *ast.Graph, vertex ast.Vertex) error { + switch vertex := vertex.(type) { + case *ast.Node: + return checkNode(graph, vertex) + case *ast.Subgraph: + return checkSubgraph(graph, vertex) + default: + panic(fmt.Sprintf("support for vertex of type %T not yet supported", vertex)) + } +} + +// checNode validates the semantics of the given node. +func checkNode(graph *ast.Graph, node *ast.Node) error { + // TODO: Check node.ID for duplicates? + // TODO: Validate node.Port. + return nil +} diff --git a/vendor/github.com/gonum/graph/formats/dot/testdata/.gitignore b/vendor/github.com/gonum/graph/formats/dot/testdata/.gitignore new file mode 100644 index 000000000000..90453be2012f --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/testdata/.gitignore @@ -0,0 +1,5 @@ +*.dot +*.png +graphviz +input +output diff --git a/vendor/github.com/gonum/graph/formats/dot/testdata/Makefile b/vendor/github.com/gonum/graph/formats/dot/testdata/Makefile new file mode 100644 index 000000000000..c3bfcb9b69f7 --- /dev/null +++ b/vendor/github.com/gonum/graph/formats/dot/testdata/Makefile @@ -0,0 +1,106 @@ +# Dependencies: +# +# * imgcmp +# go get github.com/mewkiz/cmd/imgcmp +# * dotfmt +# go get github.com/graphism/dot/cmd/dotfmt +# * dot +# sudo pacman -S graphviz +# * recode +# sudo pacman -S recode + +DOT=$(wildcard *.dot) + +# Skip DOT files for which the generated PNG images mismatch. +# +# ref: https://github.com/graphism/dot/issues/2 +# +# pixel colors differ at x=550, y=1885 +DOT:=$(filter-out b51.dot, $(DOT)) +# pixel colors differ at x=5395, y=1920 +DOT:=$(filter-out b106.dot, $(DOT)) + +# Skip segfaulting files. +# +# Segmentation fault (core dumped) +DOT:=$(filter-out b15.dot, $(DOT)) +# Segmentation fault (core dumped) +DOT:=$(filter-out b81.dot, $(DOT)) +# *** stack smashing detected ***: dot terminated +DOT:=$(filter-out sides.dot, $(DOT)) +# *** stack smashing detected ***: dot terminated +DOT:=$(filter-out tee.dot, $(DOT)) + +# Skip DOT files above 100 kB. +DOT:=$(filter-out 4elt.dot, $(DOT)) +DOT:=$(filter-out b29.dot, $(DOT)) +DOT:=$(filter-out b81.dot, $(DOT)) +DOT:=$(filter-out b100.dot, $(DOT)) +DOT:=$(filter-out b102.dot, $(DOT)) +DOT:=$(filter-out b103.dot, $(DOT)) +DOT:=$(filter-out b104.dot, $(DOT)) +DOT:=$(filter-out root.dot, $(DOT)) +DOT:=$(filter-out root_circo.dot, $(DOT)) +DOT:=$(filter-out root_twopi.dot, $(DOT)) + +# Skip invalid DOT file. +# +# Error: No or improper image file="eqn.png" +# in label of node struct1 +DOT:=$(filter-out html4.dot, $(DOT)) + +# Skip multi-graph DOT file which outputs to standard output. +DOT:=$(filter-out multi.dot, $(DOT)) + +# *.dot -> *.png +PNG=$(DOT:.dot=.png) + +INPUT_PNG=$(addprefix input/,$(PNG)) +OUTPUT_PNG=$(addprefix output/,$(PNG)) + +all: + +test: input $(INPUT_PNG) output $(OUTPUT_PNG) + @echo "PASS" + +input: + mkdir -p $@ + dot -V + +input/%.png: %.dot + dot -Tpng -o $@ $< + +output: + mkdir -p $@ + +output/%.png: %.dot + dotfmt -o "output/$<" $< + dot -Tpng -o $@ "output/$<" + imgcmp "input/$(notdir $@)" $@ + +fetch: graphviz + # Copy *.gv and *.dot files. + find graphviz -type f -name '*.gv' -not -wholename "graphviz/rtest/share/b545.gv" -not -name "base.gv" | xargs -I '{}' cp "{}" . + find graphviz -type f -name '*.dot' | xargs -I '{}' cp "{}" . + + # Rename *.gv to *.dot. + #rename .gv .dot *.gv + ls *.gv | xargs -I '{}' basename "{}" .gv | xargs -I '{}' mv "{}.gv" "{}.dot" + + # Remove execute permissions. + chmod 0644 *.dot + + # Convert Latin1 encoded files to UTF-8. + grep -l "charset=latin1" *.dot | xargs -I '{}' recode ISO-8859-1..UTF8 "{}" + recode ISO-8859-1..UTF8 Latin1.dot + + # Clean up. + rm -rf graphviz + +graphviz: + git clone https://github.com/ellson/graphviz.git + +clean: + rm -rf *.dot input output + +.PHONY: all test fetch clean diff --git a/vendor/github.com/gonum/graph/graph.go b/vendor/github.com/gonum/graph/graph.go index 732fd269243b..adade5d79bbb 100644 --- a/vendor/github.com/gonum/graph/graph.go +++ b/vendor/github.com/gonum/graph/graph.go @@ -4,8 +4,6 @@ package graph -import "math" - // Node is a graph node. It returns a graph-unique integer ID. type Node interface { ID() int @@ -17,6 +15,7 @@ type Node interface { type Edge interface { From() Node To() Node + Weight() float64 } // Graph is a generalized graph. @@ -31,9 +30,9 @@ type Graph interface { // from the given node. From(Node) []Node - // HasEdge returns whether an edge exists between + // HasEdgeBeteen returns whether an edge exists between // nodes x and y without considering direction. - HasEdge(x, y Node) bool + HasEdgeBetween(x, y Node) bool // Edge returns the edge from u to v if such an edge // exists and nil otherwise. The node v must be directly @@ -64,110 +63,91 @@ type Directed interface { // Weighter defines graphs that can report edge weights. type Weighter interface { - // Weight returns the weight for the given edge. - Weight(Edge) float64 + // Weight returns the weight for the edge between + // x and y if Edge(x, y) returns a non-nil Edge. + // If x and y are the same node or there is no + // joining edge between the two nodes the weight + // value returned is implementation dependent. + // Weight returns true if an edge exists between + // x and y or if x and y have the same ID, false + // otherwise. + Weight(x, y Node) (w float64, ok bool) } -// Mutable is an interface for generalized graph mutation. -type Mutable interface { +// NodeAdder is an interface for adding arbitrary nodes to a graph. +type NodeAdder interface { // NewNodeID returns a new unique arbitrary ID. NewNodeID() int // Adds a node to the graph. AddNode panics if // the added node ID matches an existing node ID. AddNode(Node) +} +// NodeRemover is an interface for removing nodes from a graph. +type NodeRemover interface { // RemoveNode removes a node from the graph, as // well as any edges attached to it. If the node // is not in the graph it is a no-op. RemoveNode(Node) +} +// EdgeSetter is an interface for adding edges to a graph. +type EdgeSetter interface { // SetEdge adds an edge from one node to another. - // If the nodes do not exist, they are added. - // SetEdge will panic if the IDs of the e.From - // and e.To are equal. - SetEdge(e Edge, cost float64) + // If the graph supports node addition the nodes + // will be added if they do not exist, otherwise + // SetEdge will panic. + // If the IDs returned by e.From and e.To are + // equal, SetEdge will panic. + SetEdge(e Edge) +} +// EdgeRemover is an interface for removing nodes from a graph. +type EdgeRemover interface { // RemoveEdge removes the given edge, leaving the // terminal nodes. If the edge does not exist it // is a no-op. RemoveEdge(Edge) } -// MutableUndirected is an undirected graph that can be arbitrarily altered. -type MutableUndirected interface { - Undirected - Mutable +// Builder is a graph that can have nodes and edges added. +type Builder interface { + NodeAdder + EdgeSetter } -// MutableDirected is a directed graph that can be arbitrarily altered. -type MutableDirected interface { - Directed - Mutable +// UndirectedBuilder is an undirected graph builder. +type UndirectedBuilder interface { + Undirected + Builder } -// WeightFunc is a mapping between an edge and an edge weight. -type WeightFunc func(Edge) float64 - -// UniformCost is a WeightFunc that returns an edge cost of 1 for a non-nil Edge -// and Inf for a nil Edge. -func UniformCost(e Edge) float64 { - if e == nil { - return math.Inf(1) - } - return 1 +// DirectedBuilder is a directed graph builder. +type DirectedBuilder interface { + Directed + Builder } -// CopyUndirected copies nodes and edges as undirected edges from the source to the -// destination without first clearing the destination. CopyUndirected will panic if -// a node ID in the source graph matches a node ID in the destination. If the source -// does not implement Weighter, UniformCost is used to define edge weights. +// Copy copies nodes and edges as undirected edges from the source to the destination +// without first clearing the destination. Copy will panic if a node ID in the source +// graph matches a node ID in the destination. // -// Note that if the source is a directed graph and a fundamental cycle exists with -// two nodes where the edge weights differ, the resulting destination graph's edge -// weight between those nodes is undefined. -func CopyUndirected(dst MutableUndirected, src Graph) { - var weight WeightFunc - if g, ok := src.(Weighter); ok { - weight = g.Weight - } else { - weight = UniformCost - } - - nodes := src.Nodes() - for _, n := range nodes { - dst.AddNode(n) - } - for _, u := range nodes { - for _, v := range src.From(u) { - edge := src.Edge(u, v) - dst.SetEdge(edge, weight(edge)) - } - } -} - -// CopyDirected copies nodes and edges as directed edges from the source to the -// destination without first clearing the destination. CopyDirected will panic if -// a node ID in the source graph matches a node ID in the destination. If the -// source is undirected both directions will be present in the destination after -// the copy is complete. If the source does not implement Weighter, UniformCost -// is used to define edge weights. -func CopyDirected(dst MutableDirected, src Graph) { - var weight WeightFunc - if g, ok := src.(Weighter); ok { - weight = g.Weight - } else { - weight = UniformCost - } - +// If the source is undirected and the destination is directed both directions will +// be present in the destination after the copy is complete. +// +// If the source is a directed graph, the destination is undirected, and a fundamental +// cycle exists with two nodes where the edge weights differ, the resulting destination +// graph's edge weight between those nodes is undefined. If there is a defined function +// to resolve such conflicts, an Undirect may be used to do this. +func Copy(dst Builder, src Graph) { nodes := src.Nodes() for _, n := range nodes { dst.AddNode(n) } for _, u := range nodes { for _, v := range src.From(u) { - edge := src.Edge(u, v) - dst.SetEdge(edge, weight(edge)) + dst.SetEdge(src.Edge(u, v)) } } } diff --git a/vendor/github.com/gonum/graph/graphs/gen/batagelj_brandes.go b/vendor/github.com/gonum/graph/graphs/gen/batagelj_brandes.go new file mode 100644 index 000000000000..942954de11d9 --- /dev/null +++ b/vendor/github.com/gonum/graph/graphs/gen/batagelj_brandes.go @@ -0,0 +1,357 @@ +// 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. + +// The functions in this file are random graph generators from the paper +// by Batagelj and Brandes http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf + +package gen + +import ( + "fmt" + "math" + "math/rand" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" +) + +// Gnp constructs a Gilbert’s model graph in the destination, dst, of order n. Edges +// between nodes are formed with the probability, p. If src is not nil it is used +// as the random source, otherwise rand.Float64 is used. The graph is constructed +// in O(n+m) time where m is the number of edges added. +func Gnp(dst GraphBuilder, n int, p float64, src *rand.Rand) error { + if p == 0 { + return nil + } + if p < 0 || p > 1 { + return fmt.Errorf("gen: bad probability: p=%v", p) + } + var r func() float64 + if src == nil { + r = rand.Float64 + } else { + r = src.Float64 + } + + for i := 0; i < n; i++ { + if !dst.Has(simple.Node(i)) { + dst.AddNode(simple.Node(i)) + } + } + + lp := math.Log(1 - p) + + // Add forward edges for all graphs. + for v, w := 1, -1; v < n; { + w += 1 + int(math.Log(1-r())/lp) + for w >= v && v < n { + w -= v + v++ + } + if v < n { + dst.SetEdge(simple.Edge{F: simple.Node(w), T: simple.Node(v), W: 1}) + } + } + + // Add backward edges for directed graphs. + if _, ok := dst.(graph.Directed); !ok { + return nil + } + for v, w := 1, -1; v < n; { + w += 1 + int(math.Log(1-r())/lp) + for w >= v && v < n { + w -= v + v++ + } + if v < n { + dst.SetEdge(simple.Edge{F: simple.Node(v), T: simple.Node(w), W: 1}) + } + } + + return nil +} + +// edgeNodesFor returns the pair of nodes for the ith edge in a simple +// undirected graph. The pair is returned such that w.ID < v.ID. +func edgeNodesFor(i int) (v, w simple.Node) { + // This is an algebraic simplification of the expressions described + // on p3 of http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf + v = simple.Node(0.5 + math.Sqrt(float64(1+8*i))/2) + w = simple.Node(i) - v*(v-1)/2 + return v, w +} + +// Gnm constructs a Erdős-Rényi model graph in the destination, dst, of +// order n and size m. If src is not nil it is used as the random source, +// otherwise rand.Intn is used. The graph is constructed in O(m) expected +// time for m ≤ (n choose 2)/2. +func Gnm(dst GraphBuilder, n, m int, src *rand.Rand) error { + if m == 0 { + return nil + } + + hasEdge := dst.HasEdgeBetween + d, isDirected := dst.(graph.Directed) + if isDirected { + m /= 2 + hasEdge = d.HasEdgeFromTo + } + + nChoose2 := (n - 1) * n / 2 + if m < 0 || m > nChoose2 { + return fmt.Errorf("gen: bad size: m=%d", m) + } + + var rnd func(int) int + if src == nil { + rnd = rand.Intn + } else { + rnd = src.Intn + } + + for i := 0; i < n; i++ { + if !dst.Has(simple.Node(i)) { + dst.AddNode(simple.Node(i)) + } + } + + // Add forward edges for all graphs. + for i := 0; i < m; i++ { + for { + v, w := edgeNodesFor(rnd(nChoose2)) + e := simple.Edge{F: w, T: v, W: 1} + if !hasEdge(e.F, e.T) { + dst.SetEdge(e) + break + } + } + } + + // Add backward edges for directed graphs. + if !isDirected { + return nil + } + for i := 0; i < m; i++ { + for { + v, w := edgeNodesFor(rnd(nChoose2)) + e := simple.Edge{F: v, T: w, W: 1} + if !hasEdge(e.F, e.T) { + dst.SetEdge(e) + break + } + } + } + + return nil +} + +// SmallWorldsBB constructs a small worlds graph of order n in the destination, dst. +// Node degree is specified by d and edge replacement by the probability, p. +// If src is not nil it is used as the random source, otherwise rand.Float64 is used. +// The graph is constructed in O(nd) time. +// +// The algorithm used is described in http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf +func SmallWorldsBB(dst GraphBuilder, n, d int, p float64, src *rand.Rand) error { + if d < 1 || d > (n-1)/2 { + return fmt.Errorf("gen: bad degree: d=%d", d) + } + if p == 0 { + return nil + } + if p < 0 || p >= 1 { + return fmt.Errorf("gen: bad replacement: p=%v", p) + } + var ( + rnd func() float64 + rndN func(int) int + ) + if src == nil { + rnd = rand.Float64 + rndN = rand.Intn + } else { + rnd = src.Float64 + rndN = src.Intn + } + + hasEdge := dst.HasEdgeBetween + dg, isDirected := dst.(graph.Directed) + if isDirected { + hasEdge = dg.HasEdgeFromTo + } + + for i := 0; i < n; i++ { + if !dst.Has(simple.Node(i)) { + dst.AddNode(simple.Node(i)) + } + } + + nChoose2 := (n - 1) * n / 2 + + lp := math.Log(1 - p) + + // Add forward edges for all graphs. + k := int(math.Log(1-rnd()) / lp) + m := 0 + replace := make(map[int]int) + for v := 0; v < n; v++ { + for i := 1; i <= d; i++ { + if k > 0 { + j := v*(v-1)/2 + (v+i)%n + ej := simple.Edge{W: 1} + ej.T, ej.F = edgeNodesFor(j) + if !hasEdge(ej.From(), ej.To()) { + dst.SetEdge(ej) + } + k-- + m++ + em := simple.Edge{W: 1} + em.T, em.F = edgeNodesFor(m) + if !hasEdge(em.From(), em.To()) { + replace[j] = m + } else { + replace[j] = replace[m] + } + } else { + k = int(math.Log(1-rnd()) / lp) + } + } + } + for i := m + 1; i <= n*d && i < nChoose2; i++ { + r := rndN(nChoose2-i) + i + er := simple.Edge{W: 1} + er.T, er.F = edgeNodesFor(r) + if !hasEdge(er.From(), er.To()) { + dst.SetEdge(er) + } else { + er.T, er.F = edgeNodesFor(replace[r]) + if !hasEdge(er.From(), er.To()) { + dst.SetEdge(er) + } + } + ei := simple.Edge{W: 1} + ei.T, ei.F = edgeNodesFor(i) + if !hasEdge(ei.From(), ei.To()) { + replace[r] = i + } else { + replace[r] = replace[i] + } + } + + // Add backward edges for directed graphs. + if !isDirected { + return nil + } + k = int(math.Log(1-rnd()) / lp) + m = 0 + replace = make(map[int]int) + for v := 0; v < n; v++ { + for i := 1; i <= d; i++ { + if k > 0 { + j := v*(v-1)/2 + (v+i)%n + ej := simple.Edge{W: 1} + ej.F, ej.T = edgeNodesFor(j) + if !hasEdge(ej.From(), ej.To()) { + dst.SetEdge(ej) + } + k-- + m++ + if !hasEdge(edgeNodesFor(m)) { + replace[j] = m + } else { + replace[j] = replace[m] + } + } else { + k = int(math.Log(1-rnd()) / lp) + } + } + } + for i := m + 1; i <= n*d && i < nChoose2; i++ { + r := rndN(nChoose2-i) + i + er := simple.Edge{W: 1} + er.F, er.T = edgeNodesFor(r) + if !hasEdge(er.From(), er.To()) { + dst.SetEdge(er) + } else { + er.F, er.T = edgeNodesFor(replace[r]) + if !hasEdge(er.From(), er.To()) { + dst.SetEdge(er) + } + } + if !hasEdge(edgeNodesFor(i)) { + replace[r] = i + } else { + replace[r] = replace[i] + } + } + + return nil +} + +/* +// Multigraph generators. + +type EdgeAdder interface { + AddEdge(graph.Edge) +} + +func PreferentialAttachment(dst EdgeAdder, n, d int, src *rand.Rand) { + if d < 1 { + panic("gen: bad d") + } + var rnd func(int) int + if src == nil { + rnd = rand.Intn + } else { + rnd = src.Intn + } + + m := make([]simple.Node, 2*n*d) + for v := 0; v < n; v++ { + for i := 0; i < d; i++ { + m[2*(v*d+i)] = simple.Node(v) + m[2*(v*d+i)+1] = simple.Node(m[rnd(2*v*d+i+1)]) + } + } + for i := 0; i < n*d; i++ { + dst.AddEdge(simple.Edge{F: m[2*i], T: m[2*i+1], W: 1}) + } +} + +func BipartitePreferentialAttachment(dst EdgeAdder, n, d int, src *rand.Rand) { + if d < 1 { + panic("gen: bad d") + } + var rnd func(int) int + if src == nil { + rnd = rand.Intn + } else { + rnd = src.Intn + } + + m1 := make([]simple.Node, 2*n*d) + m2 := make([]simple.Node, 2*n*d) + for v := 0; v < n; v++ { + for i := 0; i < d; i++ { + m1[2*(v*d+i)] = simple.Node(v) + m2[2*(v*d+i)] = simple.Node(n + v) + + if r := rnd(2*v*d + i + 1); r&0x1 == 0 { + m1[2*(v*d+i)+1] = m2[r] + } else { + m1[2*(v*d+i)+1] = m1[r] + } + + if r := rnd(2*v*d + i + 1); r&0x1 == 0 { + m2[2*(v*d+i)+1] = m1[r] + } else { + m2[2*(v*d+i)+1] = m2[r] + } + } + } + for i := 0; i < n*d; i++ { + dst.AddEdge(simple.Edge{F: m1[2*i], T: m1[2*i+1], W: 1}) + dst.AddEdge(simple.Edge{F: m2[2*i], T: m2[2*i+1], W: 1}) + } +} +*/ diff --git a/vendor/github.com/gonum/graph/graphs/gen/batagelj_brandes_test.go b/vendor/github.com/gonum/graph/graphs/gen/batagelj_brandes_test.go new file mode 100644 index 000000000000..21603b4fe442 --- /dev/null +++ b/vendor/github.com/gonum/graph/graphs/gen/batagelj_brandes_test.go @@ -0,0 +1,175 @@ +// 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 ( + "math" + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" +) + +type gnUndirected struct { + graph.UndirectedBuilder + addBackwards bool + addSelfLoop bool + addMultipleEdge bool +} + +func (g *gnUndirected) SetEdge(e graph.Edge) { + switch { + case e.From().ID() == e.To().ID(): + g.addSelfLoop = true + return + case e.From().ID() > e.To().ID(): + g.addBackwards = true + case g.UndirectedBuilder.HasEdgeBetween(e.From(), e.To()): + g.addMultipleEdge = true + } + + g.UndirectedBuilder.SetEdge(e) +} + +type gnDirected struct { + graph.DirectedBuilder + addSelfLoop bool + addMultipleEdge bool +} + +func (g *gnDirected) SetEdge(e graph.Edge) { + switch { + case e.From().ID() == e.To().ID(): + g.addSelfLoop = true + return + case g.DirectedBuilder.HasEdgeFromTo(e.From(), e.To()): + g.addMultipleEdge = true + } + + g.DirectedBuilder.SetEdge(e) +} + +func TestGnpUndirected(t *testing.T) { + for n := 2; n <= 20; n++ { + for p := 0.; p <= 1; p += 0.1 { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := Gnp(g, n, p, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: n=%d, p=%v", n, p) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, p=%v", n, p) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, p=%v", n, p) + } + } + } +} + +func TestGnpDirected(t *testing.T) { + for n := 2; n <= 20; n++ { + for p := 0.; p <= 1; p += 0.1 { + g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} + err := Gnp(g, n, p, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, p=%v", n, p) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, p=%v", n, p) + } + } + } +} + +func TestGnmUndirected(t *testing.T) { + for n := 2; n <= 20; n++ { + nChoose2 := (n - 1) * n / 2 + for m := 0; m <= nChoose2; m++ { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := Gnm(g, n, m, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: n=%d, m=%d", n, m) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, m=%d", n, m) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, m=%d", n, m) + } + } + } +} + +func TestGnmDirected(t *testing.T) { + for n := 2; n <= 20; n++ { + nChoose2 := (n - 1) * n / 2 + for m := 0; m <= nChoose2*2; m++ { + g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} + err := Gnm(g, n, m, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, m=%d", n, m) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, m=%d", n, m) + } + } + } +} + +func TestSmallWorldsBBUndirected(t *testing.T) { + for n := 2; n <= 20; n++ { + for d := 1; d <= (n-1)/2; d++ { + for p := 0.; p < 1; p += 0.1 { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := SmallWorldsBB(g, n, d, p, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: n=%d, d=%d, p=%v", n, d, p) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, d=%d, p=%v", n, d, p) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, d=%d, p=%v", n, d, p) + } + } + } + } +} + +func TestSmallWorldsBBDirected(t *testing.T) { + for n := 2; n <= 20; n++ { + for d := 1; d <= (n-1)/2; d++ { + for p := 0.; p < 1; p += 0.1 { + g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} + err := SmallWorldsBB(g, n, d, p, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, d=%d, p=%v", n, d, p) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, d=%d, p=%v", n, d, p) + } + } + } + } +} diff --git a/vendor/github.com/gonum/graph/graphs/gen/duplication.go b/vendor/github.com/gonum/graph/graphs/gen/duplication.go new file mode 100644 index 000000000000..d40cbedf02c1 --- /dev/null +++ b/vendor/github.com/gonum/graph/graphs/gen/duplication.go @@ -0,0 +1,125 @@ +// 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" + "math" + "math/rand" + "sort" + + "github.com/gonum/graph" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/graph/simple" +) + +// UndirectedMutator is an undirected graph builder that can remove edges. +type UndirectedMutator interface { + graph.UndirectedBuilder + graph.EdgeRemover +} + +// Duplication constructs a graph in the destination, dst, of order n. New nodes +// are created by duplicating an existing node and all its edges. Each new edge is +// deleted with probability delta. Additional edges are added between the new node +// and existing nodes with probability alpha/|V|. An exception to this addition +// rule is made for the parent node when sigma is not NaN; in this case an edge is +// created with probability sigma. With the exception of the sigma parameter, this +// corresponds to the completely correlated case in doi:10.1016/S0022-5193(03)00028-6. +// If src is not nil it is used as the random source, otherwise rand.Float64 is used. +func Duplication(dst UndirectedMutator, n int, delta, alpha, sigma float64, src *rand.Rand) error { + // As described in doi:10.1016/S0022-5193(03)00028-6 but + // also clarified in doi:10.1186/gb-2007-8-4-r51. + + if delta < 0 || delta > 1 { + return fmt.Errorf("gen: bad delta: delta=%v", delta) + } + if alpha <= 0 || alpha > 1 { + return fmt.Errorf("gen: bad alpha: alpha=%v", alpha) + } + if sigma < 0 || sigma > 1 { + return fmt.Errorf("gen: bad sigma: sigma=%v", sigma) + } + + var ( + rnd func() float64 + rndN func(int) int + ) + if src == nil { + rnd = rand.Float64 + rndN = rand.Intn + } else { + rnd = src.Float64 + rndN = src.Intn + } + + nodes := dst.Nodes() + sort.Sort(ordered.ByID(nodes)) + if len(nodes) == 0 { + n-- + dst.AddNode(simple.Node(0)) + nodes = append(nodes, simple.Node(0)) + } + for i := 0; i < n; i++ { + u := nodes[rndN(len(nodes))] + d := simple.Node(dst.NewNodeID()) + + // Add the duplicate node. + dst.AddNode(d) + + // Loop until we have connectivity + // into the rest of the graph. + for { + // Add edges to parent's neigbours. + to := dst.From(u) + sort.Sort(ordered.ByID(to)) + for _, v := range to { + if rnd() < delta || dst.HasEdgeBetween(v, d) { + continue + } + if v.ID() < d.ID() { + dst.SetEdge(simple.Edge{F: v, T: d, W: 1}) + } else { + dst.SetEdge(simple.Edge{F: d, T: v, W: 1}) + } + } + + // Add edges to old nodes. + scaledAlpha := alpha / float64(len(nodes)) + for _, v := range nodes { + switch v.ID() { + case u.ID(): + if !math.IsNaN(sigma) { + if i == 0 || rnd() < sigma { + if v.ID() < d.ID() { + dst.SetEdge(simple.Edge{F: v, T: d, W: 1}) + } else { + dst.SetEdge(simple.Edge{F: d, T: v, W: 1}) + } + } + continue + } + fallthrough + default: + if rnd() < scaledAlpha && !dst.HasEdgeBetween(v, d) { + if v.ID() < d.ID() { + dst.SetEdge(simple.Edge{F: v, T: d, W: 1}) + } else { + dst.SetEdge(simple.Edge{F: d, T: v, W: 1}) + } + } + } + } + + if len(dst.From(d)) != 0 { + break + } + } + + nodes = append(nodes, d) + } + + return nil +} diff --git a/vendor/github.com/gonum/graph/graphs/gen/duplication_test.go b/vendor/github.com/gonum/graph/graphs/gen/duplication_test.go new file mode 100644 index 000000000000..d82a1376581d --- /dev/null +++ b/vendor/github.com/gonum/graph/graphs/gen/duplication_test.go @@ -0,0 +1,59 @@ +// 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 ( + "math" + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" +) + +type duplication struct { + UndirectedMutator + addBackwards bool + addSelfLoop bool + addMultipleEdge bool +} + +func (g *duplication) SetEdge(e graph.Edge) { + switch { + case e.From().ID() == e.To().ID(): + g.addSelfLoop = true + return + case e.From().ID() > e.To().ID(): + g.addBackwards = true + case g.UndirectedMutator.HasEdgeBetween(e.From(), e.To()): + g.addMultipleEdge = true + } + + g.UndirectedMutator.SetEdge(e) +} + +func TestDuplication(t *testing.T) { + for n := 2; n <= 50; n++ { + for alpha := 0.1; alpha <= 1; alpha += 0.1 { + for delta := 0.; delta <= 1; delta += 0.2 { + for sigma := 0.; sigma <= 1; sigma += 0.2 { + g := &duplication{UndirectedMutator: simple.NewUndirectedGraph(0, math.Inf(1))} + err := Duplication(g, n, delta, alpha, sigma, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, alpha=%v, delta=%v sigma=%v: %v", n, alpha, delta, sigma, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: n=%d, alpha=%v, delta=%v sigma=%v", n, alpha, delta, sigma) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, alpha=%v, delta=%v sigma=%v", n, alpha, delta, sigma) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, alpha=%v, delta=%v sigma=%v", n, alpha, delta, sigma) + } + } + } + } + } +} diff --git a/vendor/github.com/gonum/graph/graphs/gen/gen.go b/vendor/github.com/gonum/graph/graphs/gen/gen.go new file mode 100644 index 000000000000..33cb03d91f7b --- /dev/null +++ b/vendor/github.com/gonum/graph/graphs/gen/gen.go @@ -0,0 +1,22 @@ +// 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 provides random graph generation functions. +package gen + +import "github.com/gonum/graph" + +// GraphBuilder is a graph that can have nodes and edges added. +type GraphBuilder interface { + Has(graph.Node) bool + HasEdgeBetween(x, y graph.Node) bool + graph.Builder +} + +func abs(a int) int { + if a < 0 { + return -a + } + return a +} diff --git a/vendor/github.com/gonum/graph/graphs/gen/holme_kim.go b/vendor/github.com/gonum/graph/graphs/gen/holme_kim.go new file mode 100644 index 000000000000..ded9abe7894b --- /dev/null +++ b/vendor/github.com/gonum/graph/graphs/gen/holme_kim.go @@ -0,0 +1,160 @@ +// 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 ( + "errors" + "fmt" + "math/rand" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" + "github.com/gonum/stat/sampleuv" +) + +// TunableClusteringScaleFree constructs a graph in the destination, dst, of order n. +// The graph is constructed successively starting from an m order graph with one node +// having degree m-1. At each iteration of graph addition, one node is added with m +// additional edges joining existing nodes with probability proportional to the nodes' +// degrees. The edges are formed as a triad with probability, p. +// If src is not nil it is used as the random source, otherwise rand.Float64 and +// rand.Intn are used. +// +// The algorithm is essentially as described in http://arxiv.org/abs/cond-mat/0110452. +func TunableClusteringScaleFree(dst graph.UndirectedBuilder, n, m int, p float64, src *rand.Rand) error { + if p < 0 || p > 1 { + return fmt.Errorf("gen: bad probability: p=%v", p) + } + if n <= m { + return fmt.Errorf("gen: n <= m: n=%v m=%d", n, m) + } + + var ( + rnd func() float64 + rndN func(int) int + ) + if src == nil { + rnd = rand.Float64 + rndN = rand.Intn + } else { + rnd = src.Float64 + rndN = src.Intn + } + + // Initial condition. + wt := make([]float64, n) + for u := 0; u < m; u++ { + if !dst.Has(simple.Node(u)) { + dst.AddNode(simple.Node(u)) + } + // We need to give equal probability for + // adding the first generation of edges. + wt[u] = 1 + } + ws := sampleuv.NewWeighted(wt, src) + for i := range wt { + // These weights will organically grow + // after the first growth iteration. + wt[i] = 0 + } + + // Growth. + for v := m; v < n; v++ { + var u int + pa: + for i := 0; i < m; i++ { + // Triad formation. + if i != 0 && rnd() < p { + for _, w := range permute(dst.From(simple.Node(u)), rndN) { + wid := w.ID() + if wid == v || dst.HasEdgeBetween(w, simple.Node(v)) { + continue + } + dst.SetEdge(simple.Edge{F: w, T: simple.Node(v), W: 1}) + wt[wid]++ + wt[v]++ + continue pa + } + } + + // Preferential attachment. + for { + var ok bool + u, ok = ws.Take() + if !ok { + return errors.New("gen: depleted distribution") + } + if u == v || dst.HasEdgeBetween(simple.Node(u), simple.Node(v)) { + continue + } + dst.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1}) + wt[u]++ + wt[v]++ + break + } + } + + ws.ReweightAll(wt) + } + + return nil +} + +func permute(n []graph.Node, rnd func(int) int) []graph.Node { + for i := range n[:len(n)-1] { + j := rnd(len(n)-i) + i + n[i], n[j] = n[j], n[i] + } + return n +} + +// PreferentialAttachment constructs a graph in the destination, dst, of order n. +// The graph is constructed successively starting from an m order graph with one +// node having degree m-1. At each iteration of graph addition, one node is added +// with m additional edges joining existing nodes with probability proportional +// to the nodes' degrees. If src is not nil it is used as the random source, +// otherwise rand.Float64 is used. +// +// The algorithm is essentially as described in http://arxiv.org/abs/cond-mat/0110452 +// after 10.1126/science.286.5439.509. +func PreferentialAttachment(dst graph.UndirectedBuilder, n, m int, src *rand.Rand) error { + if n <= m { + return fmt.Errorf("gen: n <= m: n=%v m=%d", n, m) + } + + // Initial condition. + wt := make([]float64, n) + for u := 0; u < m; u++ { + if !dst.Has(simple.Node(u)) { + dst.AddNode(simple.Node(u)) + } + // We need to give equal probability for + // adding the first generation of edges. + wt[u] = 1 + } + ws := sampleuv.NewWeighted(wt, src) + for i := range wt { + // These weights will organically grow + // after the first growth iteration. + wt[i] = 0 + } + + // Growth. + for v := m; v < n; v++ { + for i := 0; i < m; i++ { + // Preferential attachment. + u, ok := ws.Take() + if !ok { + return errors.New("gen: depleted distribution") + } + dst.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1}) + wt[u]++ + wt[v]++ + } + ws.ReweightAll(wt) + } + + return nil +} diff --git a/vendor/github.com/gonum/graph/graphs/gen/holme_kim_test.go b/vendor/github.com/gonum/graph/graphs/gen/holme_kim_test.go new file mode 100644 index 000000000000..567ddd52429d --- /dev/null +++ b/vendor/github.com/gonum/graph/graphs/gen/holme_kim_test.go @@ -0,0 +1,56 @@ +// 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 ( + "math" + "testing" + + "github.com/gonum/graph/simple" +) + +func TestTunableClusteringScaleFree(t *testing.T) { + for n := 2; n <= 20; n++ { + for m := 0; m < n; m++ { + for p := 0.; p <= 1; p += 0.1 { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := TunableClusteringScaleFree(g, n, m, p, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, m=%d, p=%v: %v", n, m, p, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: n=%d, m=%d, p=%v", n, m, p) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, m=%d, p=%v", n, m, p) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, m=%d, p=%v", n, m, p) + } + } + } + } +} + +func TestPreferentialAttachment(t *testing.T) { + for n := 2; n <= 20; n++ { + for m := 0; m < n; m++ { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := PreferentialAttachment(g, n, m, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: n=%d, m=%d", n, m) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, m=%d", n, m) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, m=%d", n, m) + } + } + } +} diff --git a/vendor/github.com/gonum/graph/graphs/gen/small_world.go b/vendor/github.com/gonum/graph/graphs/gen/small_world.go new file mode 100644 index 000000000000..baacca75720c --- /dev/null +++ b/vendor/github.com/gonum/graph/graphs/gen/small_world.go @@ -0,0 +1,204 @@ +// 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 ( + "errors" + "fmt" + "math" + "math/rand" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" + "github.com/gonum/stat/sampleuv" +) + +// NavigableSmallWorld constructs an N-dimensional grid with guaranteed local connectivity +// and random long-range connectivity in the destination, dst. The dims parameters specifies +// the length of each of the N dimensions, p defines the Manhattan distance between local +// nodes, and q defines the number of out-going long-range connections from each node. Long- +// range connections are made with a probability proportional to |d(u,v)|^-r where d is the +// Manhattan distance between non-local nodes. +// +// The algorithm is essentially as described on p4 of http://www.cs.cornell.edu/home/kleinber/swn.pdf. +func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src *rand.Rand) (err error) { + if p < 1 { + return fmt.Errorf("gen: bad local distance: p=%v", p) + } + if q < 0 { + return fmt.Errorf("gen: bad distant link count: q=%v", q) + } + if r < 0 { + return fmt.Errorf("gen: bad decay constant: r=%v", r) + } + + n := 1 + for _, d := range dims { + n *= d + } + for i := 0; i < n; i++ { + if !dst.Has(simple.Node(i)) { + dst.AddNode(simple.Node(i)) + } + } + + hasEdge := dst.HasEdgeBetween + d, isDirected := dst.(graph.Directed) + if isDirected { + hasEdge = d.HasEdgeFromTo + } + + locality := make([]int, len(dims)) + for i := range locality { + locality[i] = p*2 + 1 + } + iterateOver(dims, func(u []int) { + uid := idFrom(u, dims) + iterateOver(locality, func(delta []int) { + d := manhattanDelta(u, delta, dims, -p) + if d == 0 || d > p { + return + } + vid := idFromDelta(u, delta, dims, -p) + e := simple.Edge{F: simple.Node(uid), T: simple.Node(vid), W: 1} + if uid > vid { + e.F, e.T = e.T, e.F + } + if !hasEdge(e.From(), e.To()) { + dst.SetEdge(e) + } + if !isDirected { + return + } + e.F, e.T = e.T, e.F + if !hasEdge(e.From(), e.To()) { + dst.SetEdge(e) + } + }) + }) + + defer func() { + r := recover() + if r != nil { + if r != "depleted distribution" { + panic(r) + } + err = errors.New("depleted distribution") + } + }() + w := make([]float64, n) + ws := sampleuv.NewWeighted(w, src) + iterateOver(dims, func(u []int) { + uid := idFrom(u, dims) + iterateOver(dims, func(v []int) { + d := manhattanBetween(u, v) + if d <= p { + return + } + w[idFrom(v, dims)] = math.Pow(float64(d), -r) + }) + ws.ReweightAll(w) + for i := 0; i < q; i++ { + vid, ok := ws.Take() + if !ok { + panic("depleted distribution") + } + e := simple.Edge{F: simple.Node(uid), T: simple.Node(vid), W: 1} + if !isDirected && uid > vid { + e.F, e.T = e.T, e.F + } + if !hasEdge(e.From(), e.To()) { + dst.SetEdge(e) + } + } + for i := range w { + w[i] = 0 + } + }) + + return nil +} + +// iterateOver performs an iteration over all dimensions of dims, calling fn +// for each state. The elements of state must not be mutated by fn. +func iterateOver(dims []int, fn func(state []int)) { + iterator(0, dims, make([]int, len(dims)), fn) +} + +func iterator(d int, dims, state []int, fn func(state []int)) { + if d >= len(dims) { + fn(state) + return + } + for i := 0; i < dims[d]; i++ { + state[d] = i + iterator(d+1, dims, state, fn) + } +} + +// manhattanBetween returns the Manhattan distance between a and b. +func manhattanBetween(a, b []int) int { + if len(a) != len(b) { + panic("gen: unexpected dimension") + } + var d int + for i, v := range a { + d += abs(v - b[i]) + } + return d +} + +// manhattanDelta returns the Manhattan norm of delta+translate. If a +// translated by delta+translate is out of the range given by dims, +// zero is returned. +func manhattanDelta(a, delta, dims []int, translate int) int { + if len(a) != len(dims) { + panic("gen: unexpected dimension") + } + if len(delta) != len(dims) { + panic("gen: unexpected dimension") + } + var d int + for i, v := range delta { + v += translate + t := a[i] + v + if t < 0 || t >= dims[i] { + return 0 + } + d += abs(v) + } + return d +} + +// idFrom returns a node id for the slice n over the given dimensions. +func idFrom(n, dims []int) int { + s := 1 + var id int + for d, m := range dims { + p := n[d] + if p < 0 || p >= m { + panic("gen: element out of range") + } + id += p * s + s *= m + } + return id +} + +// idFromDelta returns a node id for the slice base plus the delta over the given +// dimensions and applying the translation. +func idFromDelta(base, delta, dims []int, translate int) int { + s := 1 + var id int + for d, m := range dims { + n := base[d] + delta[d] + translate + if n < 0 || n >= m { + panic("gen: element out of range") + } + id += n * s + s *= m + } + return id +} diff --git a/vendor/github.com/gonum/graph/graphs/gen/small_world_test.go b/vendor/github.com/gonum/graph/graphs/gen/small_world_test.go new file mode 100644 index 000000000000..4dfcd4d711d6 --- /dev/null +++ b/vendor/github.com/gonum/graph/graphs/gen/small_world_test.go @@ -0,0 +1,73 @@ +// 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 ( + "math" + "testing" + + "github.com/gonum/graph/simple" +) + +var smallWorldDimensionParameters = [][]int{ + {50}, + {10, 10}, + {6, 5, 4}, +} + +func TestNavigableSmallWorldUndirected(t *testing.T) { + for p := 1; p < 5; p++ { + for q := 0; q < 10; q++ { + for r := 0.5; r < 10; r++ { + for _, dims := range smallWorldDimensionParameters { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := NavigableSmallWorld(g, dims, p, q, r, nil) + n := 1 + for _, d := range dims { + n *= d + } + if err != nil { + t.Fatalf("unexpected error: dims=%v n=%d, p=%d, q=%d, r=%v: %v", dims, n, p, q, r, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) + } + } + } + } + } +} + +func TestNavigableSmallWorldDirected(t *testing.T) { + for p := 1; p < 5; p++ { + for q := 0; q < 10; q++ { + for r := 0.5; r < 10; r++ { + for _, dims := range smallWorldDimensionParameters { + g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} + err := NavigableSmallWorld(g, dims, p, q, r, nil) + n := 1 + for _, d := range dims { + n *= d + } + if err != nil { + t.Fatalf("unexpected error: dims=%v n=%d, p=%d, q=%d, r=%v, r=%v: %v", dims, n, p, q, r, err) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) + } + } + } + } + } +} diff --git a/vendor/github.com/gonum/graph/internal/linear.go b/vendor/github.com/gonum/graph/internal/linear/linear.go similarity index 95% rename from vendor/github.com/gonum/graph/internal/linear.go rename to vendor/github.com/gonum/graph/internal/linear/linear.go index 3d64de9cf30d..532226bb5f1e 100644 --- a/vendor/github.com/gonum/graph/internal/linear.go +++ b/vendor/github.com/gonum/graph/internal/linear/linear.go @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package internal +// Package linear provides common linear data structures. +package linear import ( "github.com/gonum/graph" diff --git a/vendor/github.com/gonum/graph/internal/ordered/sort.go b/vendor/github.com/gonum/graph/internal/ordered/sort.go new file mode 100644 index 000000000000..8cc9657e3e4f --- /dev/null +++ b/vendor/github.com/gonum/graph/internal/ordered/sort.go @@ -0,0 +1,62 @@ +// 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 ordered provides common sort ordering types. +package ordered + +import "github.com/gonum/graph" + +// ByID implements the sort.Interface sorting a slice of graph.Node +// by ID. +type ByID []graph.Node + +func (n ByID) Len() int { return len(n) } +func (n ByID) Less(i, j int) bool { return n[i].ID() < n[j].ID() } +func (n ByID) Swap(i, j int) { n[i], n[j] = n[j], n[i] } + +// BySliceValues implements the sort.Interface sorting a slice of +// []int lexically by the values of the []int. +type BySliceValues [][]int + +func (c BySliceValues) Len() int { return len(c) } +func (c BySliceValues) Less(i, j int) bool { + a, b := c[i], c[j] + l := len(a) + if len(b) < l { + l = len(b) + } + for k, v := range a[:l] { + if v < b[k] { + return true + } + if v > b[k] { + return false + } + } + return len(a) < len(b) +} +func (c BySliceValues) Swap(i, j int) { c[i], c[j] = c[j], c[i] } + +// BySliceIDs implements the sort.Interface sorting a slice of +// []graph.Node lexically by the IDs of the []graph.Node. +type BySliceIDs [][]graph.Node + +func (c BySliceIDs) Len() int { return len(c) } +func (c BySliceIDs) Less(i, j int) bool { + a, b := c[i], c[j] + l := len(a) + if len(b) < l { + l = len(b) + } + for k, v := range a[:l] { + if v.ID() < b[k].ID() { + return true + } + if v.ID() > b[k].ID() { + return false + } + } + return len(a) < len(b) +} +func (c BySliceIDs) Swap(i, j int) { c[i], c[j] = c[j], c[i] } diff --git a/vendor/github.com/gonum/graph/internal/set.go b/vendor/github.com/gonum/graph/internal/set.go deleted file mode 100644 index 3ad1bc8c4f7b..000000000000 --- a/vendor/github.com/gonum/graph/internal/set.go +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright ©2014 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 internal - -import ( - "unsafe" - - "github.com/gonum/graph" -) - -// IntSet is a set of integer identifiers. -type IntSet map[int]struct{} - -// The simple accessor methods for Set are provided to allow ease of -// implementation change should the need arise. - -// Add inserts an element into the set. -func (s IntSet) Add(e int) { - s[e] = struct{}{} -} - -// Has reports the existence of the element in the set. -func (s IntSet) Has(e int) bool { - _, ok := s[e] - return ok -} - -// Remove deletes the specified element from the set. -func (s IntSet) Remove(e int) { - delete(s, e) -} - -// Count reports the number of elements stored in the set. -func (s IntSet) Count() int { - return len(s) -} - -// Same determines whether two sets are backed by the same store. In the -// current implementation using hash maps it makes use of the fact that -// hash maps (at least in the gc implementation) are passed as a pointer -// to a runtime Hmap struct. -// -// A map is not seen by the runtime as a pointer though, so we cannot -// directly compare the sets converted to unsafe.Pointer and need to take -// the sets' addressed and dereference them as pointers to some comparable -// type. -func Same(s1, s2 Set) bool { - return *(*uintptr)(unsafe.Pointer(&s1)) == *(*uintptr)(unsafe.Pointer(&s2)) -} - -// A set is a set of nodes keyed in their integer identifiers. -type Set map[int]graph.Node - -// The simple accessor methods for Set are provided to allow ease of -// implementation change should the need arise. - -// Add inserts an element into the set. -func (s Set) Add(n graph.Node) { - s[n.ID()] = n -} - -// Remove deletes the specified element from the set. -func (s Set) Remove(e graph.Node) { - delete(s, e.ID()) -} - -// Has reports the existence of the element in the set. -func (s Set) Has(n graph.Node) bool { - _, ok := s[n.ID()] - return ok -} - -// Clear returns an empty set, possibly using the same backing store. -// Clear is not provided as a method since there is no way to replace -// the calling value if clearing is performed by a make(set). Clear -// should never be called without keeping the returned value. -func Clear(s Set) Set { - if len(s) == 0 { - return s - } - - return make(Set) -} - -// Copy performs a perfect copy from s1 to dst (meaning the sets will -// be equal). -func (dst Set) Copy(src Set) Set { - if Same(src, dst) { - return dst - } - - if len(dst) > 0 { - dst = make(Set, len(src)) - } - - for e, n := range src { - dst[e] = n - } - - return dst -} - -// Equal reports set equality between the parameters. Sets are equal if -// and only if they have the same elements. -func Equal(s1, s2 Set) bool { - if Same(s1, s2) { - return true - } - - if len(s1) != len(s2) { - return false - } - - for e := range s1 { - if _, ok := s2[e]; !ok { - return false - } - } - - return true -} - -// Union takes the union of s1 and s2, and stores it in dst. -// -// The union of two sets, s1 and s2, is the set containing all the -// elements of each, for instance: -// -// {a,b,c} UNION {d,e,f} = {a,b,c,d,e,f} -// -// Since sets may not have repetition, unions of two sets that overlap -// do not contain repeat elements, that is: -// -// {a,b,c} UNION {b,c,d} = {a,b,c,d} -// -func (dst Set) Union(s1, s2 Set) Set { - if Same(s1, s2) { - return dst.Copy(s1) - } - - if !Same(s1, dst) && !Same(s2, dst) { - dst = Clear(dst) - } - - if !Same(dst, s1) { - for e, n := range s1 { - dst[e] = n - } - } - - if !Same(dst, s2) { - for e, n := range s2 { - dst[e] = n - } - } - - return dst -} - -// Intersect takes the intersection of s1 and s2, and stores it in dst. -// -// The intersection of two sets, s1 and s2, is the set containing all -// the elements shared between the two sets, for instance: -// -// {a,b,c} INTERSECT {b,c,d} = {b,c} -// -// The intersection between a set and itself is itself, and thus -// effectively a copy operation: -// -// {a,b,c} INTERSECT {a,b,c} = {a,b,c} -// -// The intersection between two sets that share no elements is the empty -// set: -// -// {a,b,c} INTERSECT {d,e,f} = {} -// -func (dst Set) Intersect(s1, s2 Set) Set { - var swap Set - - if Same(s1, s2) { - return dst.Copy(s1) - } - if Same(s1, dst) { - swap = s2 - } else if Same(s2, dst) { - swap = s1 - } else { - dst = Clear(dst) - - if len(s1) > len(s2) { - s1, s2 = s2, s1 - } - - for e, n := range s1 { - if _, ok := s2[e]; ok { - dst[e] = n - } - } - - return dst - } - - for e := range dst { - if _, ok := swap[e]; !ok { - delete(dst, e) - } - } - - return dst -} diff --git a/vendor/github.com/gonum/graph/internal/set/same.go b/vendor/github.com/gonum/graph/internal/set/same.go new file mode 100644 index 000000000000..d2555782dc3e --- /dev/null +++ b/vendor/github.com/gonum/graph/internal/set/same.go @@ -0,0 +1,18 @@ +// Copyright ©2014 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. + +//+build !appengine + +package set + +import "unsafe" + +// same determines whether two sets are backed by the same store. In the +// current implementation using hash maps it makes use of the fact that +// hash maps are passed as a pointer to a runtime Hmap struct. A map is +// not seen by the runtime as a pointer though, so we use unsafe to get +// the maps' pointer values to compare. +func same(a, b Nodes) bool { + return *(*uintptr)(unsafe.Pointer(&a)) == *(*uintptr)(unsafe.Pointer(&b)) +} diff --git a/vendor/github.com/gonum/graph/internal/set/same_appengine.go b/vendor/github.com/gonum/graph/internal/set/same_appengine.go new file mode 100644 index 000000000000..537804112c92 --- /dev/null +++ b/vendor/github.com/gonum/graph/internal/set/same_appengine.go @@ -0,0 +1,18 @@ +// Copyright ©2014 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. + +//+build appengine + +package set + +import "reflect" + +// same determines whether two sets are backed by the same store. In the +// current implementation using hash maps it makes use of the fact that +// hash maps are passed as a pointer to a runtime Hmap struct. A map is +// not seen by the runtime as a pointer though, so we use reflect to get +// the maps' pointer values to compare. +func same(a, b Nodes) bool { + return reflect.ValueOf(a).Pointer() == reflect.ValueOf(b).Pointer() +} diff --git a/vendor/github.com/gonum/graph/internal/set/set.go b/vendor/github.com/gonum/graph/internal/set/set.go new file mode 100644 index 000000000000..f2a01ea40a12 --- /dev/null +++ b/vendor/github.com/gonum/graph/internal/set/set.go @@ -0,0 +1,190 @@ +// Copyright ©2014 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 set provides integer and graph.Node sets. +package set + +import "github.com/gonum/graph" + +// Ints is a set of integer identifiers. +type Ints map[int]struct{} + +// The simple accessor methods for Ints are provided to allow ease of +// implementation change should the need arise. + +// Add inserts an element into the set. +func (s Ints) Add(e int) { + s[e] = struct{}{} +} + +// Has reports the existence of the element in the set. +func (s Ints) Has(e int) bool { + _, ok := s[e] + return ok +} + +// Remove deletes the specified element from the set. +func (s Ints) Remove(e int) { + delete(s, e) +} + +// Count reports the number of elements stored in the set. +func (s Ints) Count() int { + return len(s) +} + +// Nodes is a set of nodes keyed in their integer identifiers. +type Nodes map[int]graph.Node + +// The simple accessor methods for Nodes are provided to allow ease of +// implementation change should the need arise. + +// Add inserts an element into the set. +func (s Nodes) Add(n graph.Node) { + s[n.ID()] = n +} + +// Remove deletes the specified element from the set. +func (s Nodes) Remove(e graph.Node) { + delete(s, e.ID()) +} + +// Has reports the existence of the element in the set. +func (s Nodes) Has(n graph.Node) bool { + _, ok := s[n.ID()] + return ok +} + +// clear clears the set, possibly using the same backing store. +func (s *Nodes) clear() { + if len(*s) != 0 { + *s = make(Nodes) + } +} + +// Copy performs a perfect copy from src to dst (meaning the sets will +// be equal). +func (dst Nodes) Copy(src Nodes) Nodes { + if same(src, dst) { + return dst + } + + if len(dst) > 0 { + dst = make(Nodes, len(src)) + } + + for e, n := range src { + dst[e] = n + } + + return dst +} + +// Equal reports set equality between the parameters. Sets are equal if +// and only if they have the same elements. +func Equal(a, b Nodes) bool { + if same(a, b) { + return true + } + + if len(a) != len(b) { + return false + } + + for e := range a { + if _, ok := b[e]; !ok { + return false + } + } + + return true +} + +// Union takes the union of a and b, and stores it in dst. +// +// The union of two sets, a and b, is the set containing all the +// elements of each, for instance: +// +// {a,b,c} UNION {d,e,f} = {a,b,c,d,e,f} +// +// Since sets may not have repetition, unions of two sets that overlap +// do not contain repeat elements, that is: +// +// {a,b,c} UNION {b,c,d} = {a,b,c,d} +// +func (dst Nodes) Union(a, b Nodes) Nodes { + if same(a, b) { + return dst.Copy(a) + } + + if !same(a, dst) && !same(b, dst) { + dst.clear() + } + + if !same(dst, a) { + for e, n := range a { + dst[e] = n + } + } + + if !same(dst, b) { + for e, n := range b { + dst[e] = n + } + } + + return dst +} + +// Intersect takes the intersection of a and b, and stores it in dst. +// +// The intersection of two sets, a and b, is the set containing all +// the elements shared between the two sets, for instance: +// +// {a,b,c} INTERSECT {b,c,d} = {b,c} +// +// The intersection between a set and itself is itself, and thus +// effectively a copy operation: +// +// {a,b,c} INTERSECT {a,b,c} = {a,b,c} +// +// The intersection between two sets that share no elements is the empty +// set: +// +// {a,b,c} INTERSECT {d,e,f} = {} +// +func (dst Nodes) Intersect(a, b Nodes) Nodes { + var swap Nodes + + if same(a, b) { + return dst.Copy(a) + } + if same(a, dst) { + swap = b + } else if same(b, dst) { + swap = a + } else { + dst.clear() + + if len(a) > len(b) { + a, b = b, a + } + + for e, n := range a { + if _, ok := b[e]; ok { + dst[e] = n + } + } + + return dst + } + + for e := range dst { + if _, ok := swap[e]; !ok { + delete(dst, e) + } + } + + return dst +} diff --git a/vendor/github.com/gonum/graph/internal/set_test.go b/vendor/github.com/gonum/graph/internal/set/set_test.go similarity index 66% rename from vendor/github.com/gonum/graph/internal/set_test.go rename to vendor/github.com/gonum/graph/internal/set/set_test.go index fb396204c224..bc8de46f1a0c 100644 --- a/vendor/github.com/gonum/graph/internal/set_test.go +++ b/vendor/github.com/gonum/graph/internal/set/set_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package internal +package set import "testing" @@ -10,8 +10,8 @@ type node int func (n node) ID() int { return int(n) } -// count reports the number of elements stored in the set. -func (s Set) count() int { +// count reports the number of elements stored in the node set. +func (s Nodes) count() int { return len(s) } @@ -22,31 +22,31 @@ func (s Set) count() int { // code to look at (at least for gc) is in runtime/hashmap.{h,goc}. func TestSame(t *testing.T) { var ( - a = make(Set) - b = make(Set) + a = make(Nodes) + b = make(Nodes) c = a ) - if Same(a, b) { + if same(a, b) { t.Error("Independently created sets test as same") } - if !Same(a, c) { + if !same(a, c) { t.Error("Set copy and original test as not same.") } a.Add(node(1)) - if !Same(a, c) { + if !same(a, c) { t.Error("Set copy and original test as not same after addition.") } - if !Same(nil, nil) { + if !same(nil, nil) { t.Error("nil sets test as not same.") } - if Same(b, nil) { + if same(b, nil) { t.Error("nil and empty sets test as same.") } } func TestAdd(t *testing.T) { - s := make(Set) + s := make(Nodes) if s == nil { t.Fatal("Set cannot be created successfully") } @@ -91,7 +91,7 @@ func TestAdd(t *testing.T) { } func TestRemove(t *testing.T) { - s := make(Set) + s := make(Nodes) s.Add(node(1)) s.Add(node(3)) @@ -125,21 +125,21 @@ func TestRemove(t *testing.T) { } func TestClear(t *testing.T) { - s := make(Set) + s := make(Nodes) s.Add(node(8)) s.Add(node(9)) s.Add(node(10)) - s = Clear(s) + s.clear() if s.count() != 0 { - t.Error("Clear did not properly reset set to size 0") + t.Error("clear did not properly reset set to size 0") } } func TestSelfEqual(t *testing.T) { - s := make(Set) + s := make(Nodes) if !Equal(s, s) { t.Error("Set is not equal to itself") @@ -153,80 +153,80 @@ func TestSelfEqual(t *testing.T) { } func TestEqual(t *testing.T) { - s1 := make(Set) - s2 := make(Set) + a := make(Nodes) + b := make(Nodes) - if !Equal(s1, s2) { + if !Equal(a, b) { t.Error("Two different empty sets not equal") } - s1.Add(node(1)) - if Equal(s1, s2) { + a.Add(node(1)) + if Equal(a, b) { t.Error("Two different sets with different elements not equal") } - s2.Add(node(1)) - if !Equal(s1, s2) { + b.Add(node(1)) + if !Equal(a, b) { t.Error("Two sets with same element not equal") } } func TestCopy(t *testing.T) { - s1 := make(Set) - s2 := make(Set) + a := make(Nodes) + b := make(Nodes) - s1.Add(node(1)) - s1.Add(node(2)) - s1.Add(node(3)) + a.Add(node(1)) + a.Add(node(2)) + a.Add(node(3)) - s2.Copy(s1) + b.Copy(a) - if !Equal(s1, s2) { + if !Equal(a, b) { t.Fatalf("Two sets not equal after copy") } - s2.Remove(node(1)) + b.Remove(node(1)) - if Equal(s1, s2) { + if Equal(a, b) { t.Errorf("Mutating one set mutated another after copy") } } func TestSelfCopy(t *testing.T) { - s1 := make(Set) + a := make(Nodes) - s1.Add(node(1)) - s1.Add(node(2)) + a.Add(node(1)) + a.Add(node(2)) - s1.Copy(s1) + a.Copy(a) - if s1.count() != 2 { + if a.count() != 2 { t.Error("Something strange happened when copying into self") } } func TestUnionSame(t *testing.T) { - s1 := make(Set) - s2 := make(Set) - s3 := make(Set) + a := make(Nodes) + b := make(Nodes) + c := make(Nodes) - s1.Add(node(1)) - s1.Add(node(2)) + a.Add(node(1)) + a.Add(node(2)) - s2.Add(node(1)) - s2.Add(node(2)) + b.Add(node(1)) + b.Add(node(2)) - s3.Union(s1, s2) + c.Union(a, b) - if s3.count() != 2 { + if c.count() != 2 { t.Error("Union of same sets yields set with wrong len") } - if !s3.Has(node(1)) || !s3.Has(node(2)) { + if !c.Has(node(1)) || !c.Has(node(2)) { t.Error("Union of same sets yields wrong elements") } - for i, s := range []Set{s1, s2, s3} { + for i, s := range []Nodes{a, b, c} { for e, n := range s { if e != n.ID() { t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) @@ -236,34 +236,34 @@ func TestUnionSame(t *testing.T) { } func TestUnionDiff(t *testing.T) { - s1 := make(Set) - s2 := make(Set) - s3 := make(Set) + a := make(Nodes) + b := make(Nodes) + c := make(Nodes) - s1.Add(node(1)) - s1.Add(node(2)) + a.Add(node(1)) + a.Add(node(2)) - s2.Add(node(3)) + b.Add(node(3)) - s3.Union(s1, s2) + c.Union(a, b) - if s3.count() != 3 { + if c.count() != 3 { t.Error("Union of different sets yields set with wrong len") } - if !s3.Has(node(1)) || !s3.Has(node(2)) || !s3.Has(node(3)) { + if !c.Has(node(1)) || !c.Has(node(2)) || !c.Has(node(3)) { t.Error("Union of different sets yields set with wrong elements") } - if s1.Has(node(3)) || !s1.Has(node(2)) || !s1.Has(node(1)) || s1.count() != 2 { + if a.Has(node(3)) || !a.Has(node(2)) || !a.Has(node(1)) || a.count() != 2 { t.Error("Union of sets mutates non-destination set (argument 1)") } - if !s2.Has(node(3)) || s2.Has(node(1)) || s2.Has(node(2)) || s2.count() != 1 { + if !b.Has(node(3)) || b.Has(node(1)) || b.Has(node(2)) || b.count() != 1 { t.Error("Union of sets mutates non-destination set (argument 2)") } - for i, s := range []Set{s1, s2, s3} { + for i, s := range []Nodes{a, b, c} { for e, n := range s { if e != n.ID() { t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) @@ -273,35 +273,35 @@ func TestUnionDiff(t *testing.T) { } func TestUnionOverlapping(t *testing.T) { - s1 := make(Set) - s2 := make(Set) - s3 := make(Set) + a := make(Nodes) + b := make(Nodes) + c := make(Nodes) - s1.Add(node(1)) - s1.Add(node(2)) + a.Add(node(1)) + a.Add(node(2)) - s2.Add(node(2)) - s2.Add(node(3)) + b.Add(node(2)) + b.Add(node(3)) - s3.Union(s1, s2) + c.Union(a, b) - if s3.count() != 3 { + if c.count() != 3 { t.Error("Union of overlapping sets yields set with wrong len") } - if !s3.Has(node(1)) || !s3.Has(node(2)) || !s3.Has(node(3)) { + if !c.Has(node(1)) || !c.Has(node(2)) || !c.Has(node(3)) { t.Error("Union of overlapping sets yields set with wrong elements") } - if s1.Has(node(3)) || !s1.Has(node(2)) || !s1.Has(node(1)) || s1.count() != 2 { + if a.Has(node(3)) || !a.Has(node(2)) || !a.Has(node(1)) || a.count() != 2 { t.Error("Union of sets mutates non-destination set (argument 1)") } - if !s2.Has(node(3)) || s2.Has(node(1)) || !s2.Has(node(2)) || s2.count() != 2 { + if !b.Has(node(3)) || b.Has(node(1)) || !b.Has(node(2)) || b.count() != 2 { t.Error("Union of sets mutates non-destination set (argument 2)") } - for i, s := range []Set{s1, s2, s3} { + for i, s := range []Nodes{a, b, c} { for e, n := range s { if e != n.ID() { t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) @@ -311,27 +311,27 @@ func TestUnionOverlapping(t *testing.T) { } func TestIntersectSame(t *testing.T) { - s1 := make(Set) - s2 := make(Set) - s3 := make(Set) + a := make(Nodes) + b := make(Nodes) + c := make(Nodes) - s1.Add(node(2)) - s1.Add(node(3)) + a.Add(node(2)) + a.Add(node(3)) - s2.Add(node(2)) - s2.Add(node(3)) + b.Add(node(2)) + b.Add(node(3)) - s3.Intersect(s1, s2) + c.Intersect(a, b) - if card := s3.count(); card != 2 { + if card := c.count(); card != 2 { t.Errorf("Intersection of identical sets yields set of wrong len %d", card) } - if !s3.Has(node(2)) || !s3.Has(node(3)) { + if !c.Has(node(2)) || !c.Has(node(3)) { t.Error("Intersection of identical sets yields set of wrong elements") } - for i, s := range []Set{s1, s2, s3} { + for i, s := range []Nodes{a, b, c} { for e, n := range s { if e != n.ID() { t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) @@ -341,31 +341,31 @@ func TestIntersectSame(t *testing.T) { } func TestIntersectDiff(t *testing.T) { - s1 := make(Set) - s2 := make(Set) - s3 := make(Set) + a := make(Nodes) + b := make(Nodes) + c := make(Nodes) - s1.Add(node(2)) - s1.Add(node(3)) + a.Add(node(2)) + a.Add(node(3)) - s2.Add(node(1)) - s2.Add(node(4)) + b.Add(node(1)) + b.Add(node(4)) - s3.Intersect(s1, s2) + c.Intersect(a, b) - if card := s3.count(); card != 0 { + if card := c.count(); card != 0 { t.Errorf("Intersection of different yields non-empty set %d", card) } - if !s1.Has(node(2)) || !s1.Has(node(3)) || s1.Has(node(1)) || s1.Has(node(4)) || s1.count() != 2 { + if !a.Has(node(2)) || !a.Has(node(3)) || a.Has(node(1)) || a.Has(node(4)) || a.count() != 2 { t.Error("Intersection of sets mutates non-destination set (argument 1)") } - if s2.Has(node(2)) || s2.Has(node(3)) || !s2.Has(node(1)) || !s2.Has(node(4)) || s2.count() != 2 { + if b.Has(node(2)) || b.Has(node(3)) || !b.Has(node(1)) || !b.Has(node(4)) || b.count() != 2 { t.Error("Intersection of sets mutates non-destination set (argument 1)") } - for i, s := range []Set{s1, s2, s3} { + for i, s := range []Nodes{a, b, c} { for e, n := range s { if e != n.ID() { t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) @@ -375,35 +375,35 @@ func TestIntersectDiff(t *testing.T) { } func TestIntersectOverlapping(t *testing.T) { - s1 := make(Set) - s2 := make(Set) - s3 := make(Set) + a := make(Nodes) + b := make(Nodes) + c := make(Nodes) - s1.Add(node(2)) - s1.Add(node(3)) + a.Add(node(2)) + a.Add(node(3)) - s2.Add(node(3)) - s2.Add(node(4)) + b.Add(node(3)) + b.Add(node(4)) - s3.Intersect(s1, s2) + c.Intersect(a, b) - if card := s3.count(); card != 1 { + if card := c.count(); card != 1 { t.Errorf("Intersection of overlapping sets yields set of incorrect len %d", card) } - if !s3.Has(node(3)) { + if !c.Has(node(3)) { t.Errorf("Intersection of overlapping sets yields set with wrong element") } - if !s1.Has(node(2)) || !s1.Has(node(3)) || s1.Has(node(4)) || s1.count() != 2 { + if !a.Has(node(2)) || !a.Has(node(3)) || a.Has(node(4)) || a.count() != 2 { t.Error("Intersection of sets mutates non-destination set (argument 1)") } - if s2.Has(node(2)) || !s2.Has(node(3)) || !s2.Has(node(4)) || s2.count() != 2 { + if b.Has(node(2)) || !b.Has(node(3)) || !b.Has(node(4)) || b.count() != 2 { t.Error("Intersection of sets mutates non-destination set (argument 1)") } - for i, s := range []Set{s1, s2, s3} { + for i, s := range []Nodes{a, b, c} { for e, n := range s { if e != n.ID() { t.Error("Element ID did not match key in s%d: %d != %d", i+1, e, n.ID()) diff --git a/vendor/github.com/gonum/graph/internal/sort.go b/vendor/github.com/gonum/graph/internal/sort.go deleted file mode 100644 index 3bfee0f697fd..000000000000 --- a/vendor/github.com/gonum/graph/internal/sort.go +++ /dev/null @@ -1,28 +0,0 @@ -// 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 internal - -// BySliceValues implements the sort.Interface sorting a slice of -// []int lexically by the values of the []int. -type BySliceValues [][]int - -func (c BySliceValues) Len() int { return len(c) } -func (c BySliceValues) Less(i, j int) bool { - a, b := c[i], c[j] - l := len(a) - if len(b) < l { - l = len(b) - } - for k, v := range a[:l] { - if v < b[k] { - return true - } - if v > b[k] { - return false - } - } - return len(a) < len(b) -} -func (c BySliceValues) Swap(i, j int) { c[i], c[j] = c[j], c[i] } diff --git a/vendor/github.com/gonum/graph/network/betweenness.go b/vendor/github.com/gonum/graph/network/betweenness.go index ad167325db29..c5154c9cb26d 100644 --- a/vendor/github.com/gonum/graph/network/betweenness.go +++ b/vendor/github.com/gonum/graph/network/betweenness.go @@ -8,7 +8,7 @@ import ( "math" "github.com/gonum/graph" - "github.com/gonum/graph/internal" + "github.com/gonum/graph/internal/linear" "github.com/gonum/graph/path" ) @@ -32,16 +32,71 @@ func Betweenness(g graph.Graph) map[int]float64 { // Also note special case for sparse networks: // http://wwwold.iit.cnr.it/staff/marco.pellegrini/papiri/asonam-final.pdf - var ( - cb = make(map[int]float64) + cb := make(map[int]float64) + brandes(g, func(s graph.Node, stack linear.NodeStack, p map[int][]graph.Node, delta, sigma map[int]float64) { + for stack.Len() != 0 { + w := stack.Pop() + for _, v := range p[w.ID()] { + delta[v.ID()] += sigma[v.ID()] / sigma[w.ID()] * (1 + delta[w.ID()]) + } + if w.ID() != s.ID() { + if d := delta[w.ID()]; d != 0 { + cb[w.ID()] += d + } + } + } + }) + return cb +} +// EdgeBetweenness returns the non-zero betweenness centrality for edges in the +// unweighted graph g. For an edge e the centrality C_B is computed as +// +// C_B(e) = \sum_{s ≠ t ∈ V} (\sigma_{st}(e) / \sigma_{st}), +// +// where \sigma_{st} and \sigma_{st}(e) are the number of shortest paths from s +// to t, and the subset of those paths containing e, respectively. +// +// If g is undirected, edges are retained such that u.ID < v.ID where u and v are +// the nodes of e. +func EdgeBetweenness(g graph.Graph) map[[2]int]float64 { + // Modified from Brandes' original algorithm as described in Algorithm 7 + // with the exception that node betweenness is not calculated: + // + // http://algo.uni-konstanz.de/publications/b-vspbc-08.pdf + + _, isUndirected := g.(graph.Undirected) + cb := make(map[[2]int]float64) + brandes(g, func(s graph.Node, stack linear.NodeStack, p map[int][]graph.Node, delta, sigma map[int]float64) { + for stack.Len() != 0 { + w := stack.Pop() + for _, v := range p[w.ID()] { + c := sigma[v.ID()] / sigma[w.ID()] * (1 + delta[w.ID()]) + vid := v.ID() + wid := w.ID() + if isUndirected && wid < vid { + vid, wid = wid, vid + } + cb[[2]int{vid, wid}] += c + delta[v.ID()] += c + } + } + }) + return cb +} + +// brandes is the common code for Betweenness and EdgeBetweenness. It corresponds +// to algorithm 1 in http://algo.uni-konstanz.de/publications/b-vspbc-08.pdf with +// the accumulation loop provided by the accumulate closure. +func brandes(g graph.Graph, accumulate func(s graph.Node, stack linear.NodeStack, p map[int][]graph.Node, delta, sigma map[int]float64)) { + var ( nodes = g.Nodes() - stack internal.NodeStack + stack linear.NodeStack p = make(map[int][]graph.Node, len(nodes)) sigma = make(map[int]float64, len(nodes)) d = make(map[int]int, len(nodes)) delta = make(map[int]float64, len(nodes)) - queue internal.NodeQueue + queue linear.NodeQueue ) for _, s := range nodes { stack = stack[:0] @@ -78,21 +133,10 @@ func Betweenness(g graph.Graph) map[int]float64 { for _, v := range nodes { delta[v.ID()] = 0 } + // S returns vertices in order of non-increasing distance from s - for stack.Len() != 0 { - w := stack.Pop() - for _, v := range p[w.ID()] { - delta[v.ID()] += sigma[v.ID()] / sigma[w.ID()] * (1 + delta[w.ID()]) - } - if w.ID() != s.ID() { - if d := delta[w.ID()]; d != 0 { - cb[w.ID()] += d - } - } - } + accumulate(s, stack, p, delta, sigma) } - - return cb } // WeightedGraph is a graph with edge weights. @@ -122,17 +166,11 @@ func BetweennessWeighted(g WeightedGraph, p path.AllShortest) map[int]float64 { continue } - sID := s.ID() - tID := t.ID() - // If we have a unique path, don't do the // extra work needed to get all paths. path, _, unique := p.Between(s, t) if unique { - for _, v := range path { - if vID := v.ID(); vID == sID || vID == tID { - continue - } + for _, v := range path[1 : len(path)-1] { // For undirected graphs we double count // passage though nodes. This is consistent // with Brandes' algorithm's behaviour. @@ -145,10 +183,7 @@ func BetweennessWeighted(g WeightedGraph, p path.AllShortest) map[int]float64 { paths, _ := p.AllBetween(s, t) stFrac := 1 / float64(len(paths)) for _, path := range paths { - for _, v := range path { - if vID := v.ID(); vID == sID || vID == tID { - continue - } + for _, v := range path[1 : len(path)-1] { cb[v.ID()] += stFrac } } @@ -157,3 +192,65 @@ func BetweennessWeighted(g WeightedGraph, p path.AllShortest) map[int]float64 { return cb } + +// EdgeBetweennessWeighted returns the non-zero betweenness centrality for edges in +// the weighted graph g. For an edge e the centrality C_B is computed as +// +// C_B(e) = \sum_{s ≠ t ∈ V} (\sigma_{st}(e) / \sigma_{st}), +// +// where \sigma_{st} and \sigma_{st}(e) are the number of shortest paths from s +// to t, and the subset of those paths containing e, respectively. +// +// If g is undirected, edges are retained such that u.ID < v.ID where u and v are +// the nodes of e. +func EdgeBetweennessWeighted(g WeightedGraph, p path.AllShortest) map[[2]int]float64 { + cb := make(map[[2]int]float64) + + _, isUndirected := g.(graph.Undirected) + nodes := g.Nodes() + for i, s := range nodes { + for j, t := range nodes { + if i == j { + continue + } + d := p.Weight(s, t) + if math.IsInf(d, 0) { + continue + } + + // If we have a unique path, don't do the + // extra work needed to get all paths. + path, _, unique := p.Between(s, t) + if unique { + for k, v := range path[1:] { + // For undirected graphs we double count + // passage though edges. This is consistent + // with Brandes' algorithm's behaviour. + uid := path[k].ID() + vid := v.ID() + if isUndirected && vid < uid { + uid, vid = vid, uid + } + cb[[2]int{uid, vid}]++ + } + continue + } + + // Otherwise iterate over all paths. + paths, _ := p.AllBetween(s, t) + stFrac := 1 / float64(len(paths)) + for _, path := range paths { + for k, v := range path[1:] { + uid := path[k].ID() + vid := v.ID() + if isUndirected && vid < uid { + uid, vid = vid, uid + } + cb[[2]int{uid, vid}] += stFrac + } + } + } + } + + return cb +} diff --git a/vendor/github.com/gonum/graph/network/betweenness_test.go b/vendor/github.com/gonum/graph/network/betweenness_test.go index 928a8318bc0b..0c5d6db1a4d7 100644 --- a/vendor/github.com/gonum/graph/network/betweenness_test.go +++ b/vendor/github.com/gonum/graph/network/betweenness_test.go @@ -5,19 +5,22 @@ package network import ( + "fmt" "math" + "sort" "testing" "github.com/gonum/floats" - "github.com/gonum/graph/concrete" "github.com/gonum/graph/path" + "github.com/gonum/graph/simple" ) var betweennessTests = []struct { g []set - wantTol float64 - want map[int]float64 + wantTol float64 + want map[int]float64 + wantEdges map[[2]int]float64 }{ { // Example graph from http://en.wikipedia.org/wiki/File:PageRanks-Example.svg 16:17, 8 July 2009 @@ -41,6 +44,23 @@ var betweennessTests = []struct { D: 18, E: 48, }, + wantEdges: map[[2]int]float64{ + [2]int{A, D}: 20, + [2]int{B, C}: 20, + [2]int{B, D}: 16, + [2]int{B, E}: 12, + [2]int{B, F}: 9, + [2]int{B, G}: 9, + [2]int{B, H}: 9, + [2]int{B, I}: 9, + [2]int{D, E}: 20, + [2]int{E, F}: 11, + [2]int{E, G}: 11, + [2]int{E, H}: 11, + [2]int{E, I}: 11, + [2]int{E, J}: 20, + [2]int{E, K}: 20, + }, }, { // Example graph from http://en.wikipedia.org/w/index.php?title=PageRank&oldid=659286279#Power_Method @@ -60,6 +80,15 @@ var betweennessTests = []struct { D: 2, E: 0.6667, }, + wantEdges: map[[2]int]float64{ + [2]int{A, B}: 2 + 2/3. + 4/2., + [2]int{A, C}: 2 + 2/3. + 2/2., + [2]int{A, E}: 2 + 2/3. + 2/2., + [2]int{B, D}: 2 + 2/3. + 4/2., + [2]int{C, D}: 2 + 2/3. + 2/2., + [2]int{C, E}: 2, + [2]int{D, E}: 2 + 2/3. + 2/2., + }, }, { g: []set{ @@ -72,6 +101,10 @@ var betweennessTests = []struct { want: map[int]float64{ B: 2, }, + wantEdges: map[[2]int]float64{ + [2]int{A, B}: 4, + [2]int{B, C}: 4, + }, }, { g: []set{ @@ -88,6 +121,12 @@ var betweennessTests = []struct { C: 8, D: 6, }, + wantEdges: map[[2]int]float64{ + [2]int{A, B}: 8, + [2]int{B, C}: 12, + [2]int{C, D}: 12, + [2]int{D, E}: 8, + }, }, { g: []set{ @@ -102,6 +141,12 @@ var betweennessTests = []struct { want: map[int]float64{ C: 12, }, + wantEdges: map[[2]int]float64{ + [2]int{A, C}: 8, + [2]int{B, C}: 8, + [2]int{C, D}: 8, + [2]int{C, E}: 8, + }, }, { g: []set{ @@ -114,19 +159,32 @@ var betweennessTests = []struct { wantTol: 1e-3, want: map[int]float64{}, + wantEdges: map[[2]int]float64{ + [2]int{A, B}: 2, + [2]int{A, C}: 2, + [2]int{A, D}: 2, + [2]int{A, E}: 2, + [2]int{B, C}: 2, + [2]int{B, D}: 2, + [2]int{B, E}: 2, + [2]int{C, D}: 2, + [2]int{C, E}: 2, + [2]int{D, E}: 2, + }, }, } func TestBetweenness(t *testing.T) { for i, test := range betweennessTests { - g := concrete.NewGraph() + g := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + // Weight omitted to show weight-independence. + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 0}) } } got := Betweenness(g) @@ -135,7 +193,7 @@ func TestBetweenness(t *testing.T) { wantN, gotOK := got[n] gotN, wantOK := test.want[n] if gotOK != wantOK { - t.Errorf("unexpected betweenness result for test %d, node %d", i, n) + t.Errorf("unexpected betweenness result for test %d, node %c", i, n+'A') } if !floats.EqualWithinAbsOrRel(gotN, wantN, test.wantTol, test.wantTol) { t.Errorf("unexpected betweenness result for test %d:\ngot: %v\nwant:%v", @@ -146,16 +204,49 @@ func TestBetweenness(t *testing.T) { } } +func TestEdgeBetweenness(t *testing.T) { + for i, test := range betweennessTests { + g := simple.NewUndirectedGraph(0, math.Inf(1)) + for u, e := range test.g { + // Add nodes that are not defined by an edge. + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) + } + for v := range e { + // Weight omitted to show weight-independence. + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 0}) + } + } + got := EdgeBetweenness(g) + prec := 1 - int(math.Log10(test.wantTol)) + outer: + for u := range test.g { + for v := range test.g { + wantQ, gotOK := got[[2]int{u, v}] + gotQ, wantOK := test.wantEdges[[2]int{u, v}] + if gotOK != wantOK { + t.Errorf("unexpected betweenness result for test %d, edge (%c,%c)", i, u+'A', v+'A') + } + if !floats.EqualWithinAbsOrRel(gotQ, wantQ, test.wantTol, test.wantTol) { + t.Errorf("unexpected betweenness result for test %d:\ngot: %v\nwant:%v", + i, orderedPairFloats(got, prec), orderedPairFloats(test.wantEdges, prec)) + break outer + } + } + } + } +} + func TestBetweennessWeighted(t *testing.T) { for i, test := range betweennessTests { - g := concrete.NewGraph() + g := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 1) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1}) } } @@ -171,7 +262,7 @@ func TestBetweennessWeighted(t *testing.T) { gotN, gotOK := got[n] wantN, wantOK := test.want[n] if gotOK != wantOK { - t.Errorf("unexpected betweenness existence for test %d, node %d", i, n) + t.Errorf("unexpected betweenness existence for test %d, node %c", i, n+'A') } if !floats.EqualWithinAbsOrRel(gotN, wantN, test.wantTol, test.wantTol) { t.Errorf("unexpected betweenness result for test %d:\ngot: %v\nwant:%v", @@ -181,3 +272,69 @@ func TestBetweennessWeighted(t *testing.T) { } } } + +func TestEdgeBetweennessWeighted(t *testing.T) { + for i, test := range betweennessTests { + g := simple.NewUndirectedGraph(0, math.Inf(1)) + for u, e := range test.g { + // Add nodes that are not defined by an edge. + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) + } + for v := range e { + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1}) + } + } + + p, ok := path.FloydWarshall(g) + if !ok { + t.Errorf("unexpected negative cycle in test %d", i) + continue + } + + got := EdgeBetweennessWeighted(g, p) + prec := 1 - int(math.Log10(test.wantTol)) + outer: + for u := range test.g { + for v := range test.g { + wantQ, gotOK := got[[2]int{u, v}] + gotQ, wantOK := test.wantEdges[[2]int{u, v}] + if gotOK != wantOK { + t.Errorf("unexpected betweenness result for test %d, edge (%c,%c)", i, u+'A', v+'A') + } + if !floats.EqualWithinAbsOrRel(gotQ, wantQ, test.wantTol, test.wantTol) { + t.Errorf("unexpected betweenness result for test %d:\ngot: %v\nwant:%v", + i, orderedPairFloats(got, prec), orderedPairFloats(test.wantEdges, prec)) + break outer + } + } + } + } +} + +func orderedPairFloats(w map[[2]int]float64, prec int) []pairKeyFloatVal { + o := make(orderedPairFloatsMap, 0, len(w)) + for k, v := range w { + o = append(o, pairKeyFloatVal{prec: prec, key: k, val: v}) + } + sort.Sort(o) + return o +} + +type pairKeyFloatVal struct { + prec int + key [2]int + val float64 +} + +func (kv pairKeyFloatVal) String() string { + return fmt.Sprintf("(%c,%c):%.*f", kv.key[0]+'A', kv.key[1]+'A', kv.prec, kv.val) +} + +type orderedPairFloatsMap []pairKeyFloatVal + +func (o orderedPairFloatsMap) Len() int { return len(o) } +func (o orderedPairFloatsMap) Less(i, j int) bool { + return o[i].key[0] < o[j].key[0] || (o[i].key[0] == o[j].key[0] && o[i].key[1] < o[j].key[1]) +} +func (o orderedPairFloatsMap) Swap(i, j int) { o[i], o[j] = o[j], o[i] } diff --git a/vendor/github.com/gonum/graph/network/distance_test.go b/vendor/github.com/gonum/graph/network/distance_test.go index ac769c4336c6..f06afbbc52e1 100644 --- a/vendor/github.com/gonum/graph/network/distance_test.go +++ b/vendor/github.com/gonum/graph/network/distance_test.go @@ -9,8 +9,8 @@ import ( "testing" "github.com/gonum/floats" - "github.com/gonum/graph/concrete" "github.com/gonum/graph/path" + "github.com/gonum/graph/simple" ) var undirectedCentralityTests = []struct { @@ -143,14 +143,14 @@ func TestDistanceCentralityUndirected(t *testing.T) { prec := 1 - int(math.Log10(tol)) for i, test := range undirectedCentralityTests { - g := concrete.NewGraph() + g := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 1) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1}) } } p, ok := path.FloydWarshall(g) @@ -333,14 +333,14 @@ func TestDistanceCentralityDirected(t *testing.T) { prec := 1 - int(math.Log10(tol)) for i, test := range directedCentralityTests { - g := concrete.NewDirectedGraph() + g := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 1) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1}) } } p, ok := path.FloydWarshall(g) diff --git a/vendor/github.com/gonum/graph/network/hits_test.go b/vendor/github.com/gonum/graph/network/hits_test.go index 32035bebd03b..3c20ff3d7caa 100644 --- a/vendor/github.com/gonum/graph/network/hits_test.go +++ b/vendor/github.com/gonum/graph/network/hits_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/gonum/floats" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" ) var hitsTests = []struct { @@ -43,14 +43,14 @@ var hitsTests = []struct { func TestHITS(t *testing.T) { for i, test := range hitsTests { - g := concrete.NewDirectedGraph() + g := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } got := HITS(g, test.tol) diff --git a/vendor/github.com/gonum/graph/network/page.go b/vendor/github.com/gonum/graph/network/page.go index 9cc6b9ed7da4..ffa8c27f2127 100644 --- a/vendor/github.com/gonum/graph/network/page.go +++ b/vendor/github.com/gonum/graph/network/page.go @@ -72,7 +72,7 @@ func PageRank(g graph.Directed, damp, tol float64) map[int]float64 { for { lastV, v = v, lastV - v.MulVec(m, false, lastV) + v.MulVec(m, lastV) if normDiff(vec, last) < tol { break } diff --git a/vendor/github.com/gonum/graph/network/page_test.go b/vendor/github.com/gonum/graph/network/page_test.go index f7c8c87ce0fa..14ad7d159ff7 100644 --- a/vendor/github.com/gonum/graph/network/page_test.go +++ b/vendor/github.com/gonum/graph/network/page_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/gonum/floats" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" ) var pageRankTests = []struct { @@ -81,14 +81,14 @@ var pageRankTests = []struct { func TestPageRank(t *testing.T) { for i, test := range pageRankTests { - g := concrete.NewDirectedGraph() + g := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } got := PageRank(g, test.damp, test.tol) @@ -105,14 +105,14 @@ func TestPageRank(t *testing.T) { func TestPageRankSparse(t *testing.T) { for i, test := range pageRankTests { - g := concrete.NewDirectedGraph() + g := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } got := PageRankSparse(g, test.damp, test.tol) @@ -142,7 +142,7 @@ type keyFloatVal struct { val float64 } -func (kv keyFloatVal) String() string { return fmt.Sprintf("%d:%.*f", kv.key, kv.prec, kv.val) } +func (kv keyFloatVal) String() string { return fmt.Sprintf("%c:%.*f", kv.key+'A', kv.prec, kv.val) } type orderedFloatsMap []keyFloatVal diff --git a/vendor/github.com/gonum/graph/path/a_star.go b/vendor/github.com/gonum/graph/path/a_star.go index b41d19428919..5563c1f45bd1 100644 --- a/vendor/github.com/gonum/graph/path/a_star.go +++ b/vendor/github.com/gonum/graph/path/a_star.go @@ -8,18 +8,9 @@ import ( "container/heap" "github.com/gonum/graph" - "github.com/gonum/graph/internal" + "github.com/gonum/graph/internal/set" ) -// Heuristic returns an estimate of the cost of travelling between two nodes. -type Heuristic func(x, y graph.Node) float64 - -// HeuristicCoster wraps the HeuristicCost method. A graph implementing the -// interface provides a heuristic between any two given nodes. -type HeuristicCoster interface { - HeuristicCost(x, y graph.Node) float64 -} - // AStar finds the A*-shortest path from s to t in g using the heuristic h. The path and // its cost are returned in a Shortest along with paths and costs to all nodes explored // during the search. The number of expanded nodes is also returned. This value may help @@ -31,16 +22,16 @@ type HeuristicCoster interface { // // If h is nil, AStar will use the g.HeuristicCost method if g implements HeuristicCoster, // falling back to NullHeuristic otherwise. If the graph does not implement graph.Weighter, -// graph.UniformCost is used. AStar will panic if g has an A*-reachable negative edge weight. +// UniformCost is used. AStar will panic if g has an A*-reachable negative edge weight. func AStar(s, t graph.Node, g graph.Graph, h Heuristic) (path Shortest, expanded int) { if !g.Has(s) || !g.Has(t) { return Shortest{from: s}, 0 } - var weight graph.WeightFunc - if g, ok := g.(graph.Weighter); ok { - weight = g.Weight + var weight Weighting + if wg, ok := g.(graph.Weighter); ok { + weight = wg.Weight } else { - weight = graph.UniformCost + weight = UniformCost(g) } if h == nil { if g, ok := g.(HeuristicCoster); ok { @@ -53,7 +44,7 @@ func AStar(s, t graph.Node, g graph.Graph, h Heuristic) (path Shortest, expanded path = newShortestFrom(s, g.Nodes()) tid := t.ID() - visited := make(internal.IntSet) + visited := make(set.Ints) open := &aStarQueue{indexOf: make(map[int]int)} heap.Push(open, aStarNode{node: s, gscore: 0, fscore: h(s, t)}) @@ -75,7 +66,10 @@ func AStar(s, t graph.Node, g graph.Graph, h Heuristic) (path Shortest, expanded } j := path.indexOf[vid] - w := weight(g.Edge(u.node, v)) + w, ok := weight(u.node, v) + if !ok { + panic("A*: unexpected invalid weight") + } if w < 0 { panic("A*: negative edge weight") } diff --git a/vendor/github.com/gonum/graph/path/a_star_test.go b/vendor/github.com/gonum/graph/path/a_star_test.go index f7ed7e1e559b..21ad9e3a8de7 100644 --- a/vendor/github.com/gonum/graph/path/a_star_test.go +++ b/vendor/github.com/gonum/graph/path/a_star_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package path_test +package path import ( "math" @@ -10,9 +10,9 @@ import ( "testing" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" - "github.com/gonum/graph/path" "github.com/gonum/graph/path/internal" + "github.com/gonum/graph/path/internal/testgraphs" + "github.com/gonum/graph/simple" "github.com/gonum/graph/topo" ) @@ -21,7 +21,7 @@ var aStarTests = []struct { g graph.Graph s, t int - heuristic path.Heuristic + heuristic Heuristic wantPath []int }{ { @@ -127,19 +127,19 @@ var aStarTests = []struct { func TestAStar(t *testing.T) { for _, test := range aStarTests { - pt, _ := path.AStar(concrete.Node(test.s), concrete.Node(test.t), test.g, test.heuristic) + pt, _ := AStar(simple.Node(test.s), simple.Node(test.t), test.g, test.heuristic) - p, cost := pt.To(concrete.Node(test.t)) + p, cost := pt.To(simple.Node(test.t)) if !topo.IsPathIn(test.g, p) { t.Error("got path that is not path in input graph for %q", test.name) } - bfp, ok := path.BellmanFordFrom(concrete.Node(test.s), test.g) + bfp, ok := BellmanFordFrom(simple.Node(test.s), test.g) if !ok { t.Fatalf("unexpected negative cycle in %q", test.name) } - if want := bfp.WeightTo(concrete.Node(test.t)); cost != want { + if want := bfp.WeightTo(simple.Node(test.t)); cost != want { t.Errorf("unexpected cost for %q: got:%v want:%v", test.name, cost, want) } @@ -154,7 +154,7 @@ func TestAStar(t *testing.T) { } func TestExhaustiveAStar(t *testing.T) { - g := concrete.NewGraph() + g := simple.NewUndirectedGraph(0, math.Inf(1)) nodes := []locatedNode{ {id: 1, x: 0, y: 6}, {id: 2, x: 1, y: 0}, @@ -179,7 +179,7 @@ func TestExhaustiveAStar(t *testing.T) { {from: g.Node(5), to: g.Node(6), cost: 9}, } for _, e := range edges { - g.SetEdge(e, e.cost) + g.SetEdge(e) } heuristic := func(u, v graph.Node) float64 { @@ -192,10 +192,10 @@ func TestExhaustiveAStar(t *testing.T) { t.Fatalf("non-monotonic heuristic at edge:%v for goal:%v", edge, goal) } - ps := path.DijkstraAllPaths(g) + ps := DijkstraAllPaths(g) for _, start := range g.Nodes() { for _, goal := range g.Nodes() { - pt, _ := path.AStar(start, goal, g, heuristic) + pt, _ := AStar(start, goal, g, heuristic) gotPath, gotWeight := pt.To(goal) wantPath, wantWeight, _ := ps.Between(start, goal) if gotWeight != wantWeight { @@ -224,18 +224,18 @@ type weightedEdge struct { func (e weightedEdge) From() graph.Node { return e.from } func (e weightedEdge) To() graph.Node { return e.to } +func (e weightedEdge) Weight() float64 { return e.cost } -type costEdgeListGraph interface { - graph.Weighter - path.EdgeListerGraph -} - -func isMonotonic(g costEdgeListGraph, h path.Heuristic) (ok bool, at graph.Edge, goal graph.Node) { +func isMonotonic(g UndirectedWeightLister, h Heuristic) (ok bool, at graph.Edge, goal graph.Node) { for _, goal := range g.Nodes() { for _, edge := range g.Edges() { from := edge.From() to := edge.To() - if h(from, goal) > g.Weight(edge)+h(to, goal) { + w, ok := g.Weight(from, to) + if !ok { + panic("A*: unexpected invalid weight") + } + if h(from, goal) > w+h(to, goal) { return false, edge, goal } } @@ -244,14 +244,14 @@ func isMonotonic(g costEdgeListGraph, h path.Heuristic) (ok bool, at graph.Edge, } func TestAStarNullHeuristic(t *testing.T) { - for _, test := range shortestPathTests { - g := test.g() - for _, e := range test.edges { - g.SetEdge(e, e.Cost) + for _, test := range testgraphs.ShortestPathTests { + g := test.Graph() + for _, e := range test.Edges { + g.SetEdge(e) } var ( - pt path.Shortest + pt Shortest panicked bool ) @@ -259,38 +259,38 @@ func TestAStarNullHeuristic(t *testing.T) { defer func() { panicked = recover() != nil }() - pt, _ = path.AStar(test.query.From(), test.query.To(), g.(graph.Graph), nil) + pt, _ = AStar(test.Query.From(), test.Query.To(), g.(graph.Graph), nil) }() - if panicked || test.negative { - if !test.negative { - t.Errorf("%q: unexpected panic", test.name) + if panicked || test.HasNegativeWeight { + if !test.HasNegativeWeight { + t.Errorf("%q: unexpected panic", test.Name) } if !panicked { - t.Errorf("%q: expected panic for negative edge weight", test.name) + t.Errorf("%q: expected panic for negative edge weight", test.Name) } continue } - if pt.From().ID() != test.query.From().ID() { - t.Fatalf("%q: unexpected from node ID: got:%d want:%d", pt.From().ID(), test.query.From().ID()) + if pt.From().ID() != test.Query.From().ID() { + t.Fatalf("%q: unexpected from node ID: got:%d want:%d", pt.From().ID(), test.Query.From().ID()) } - p, weight := pt.To(test.query.To()) - if weight != test.weight { + p, weight := pt.To(test.Query.To()) + if weight != test.Weight { t.Errorf("%q: unexpected weight from Between: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } - if weight := pt.WeightTo(test.query.To()); weight != test.weight { + if weight := pt.WeightTo(test.Query.To()); weight != test.Weight { t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } var got []int for _, n := range p { got = append(got, n.ID()) } - ok := len(got) == 0 && len(test.want) == 0 - for _, sp := range test.want { + ok := len(got) == 0 && len(test.WantPaths) == 0 + for _, sp := range test.WantPaths { if reflect.DeepEqual(got, sp) { ok = true break @@ -298,13 +298,13 @@ func TestAStarNullHeuristic(t *testing.T) { } if !ok { t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", - test.name, p, test.want) + test.Name, p, test.WantPaths) } - np, weight := pt.To(test.none.To()) - if pt.From().ID() == test.none.From().ID() && (np != nil || !math.IsInf(weight, 1)) { + np, weight := pt.To(test.NoPathFor.To()) + if pt.From().ID() == test.NoPathFor.From().ID() && (np != nil || !math.IsInf(weight, 1)) { t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f\nwant:path= weight=+Inf", - test.name, np, weight) + test.Name, np, weight) } } } diff --git a/vendor/github.com/gonum/graph/path/bellman_ford_moore.go b/vendor/github.com/gonum/graph/path/bellman_ford_moore.go index 6ca49d5f602d..86bcf728a2db 100644 --- a/vendor/github.com/gonum/graph/path/bellman_ford_moore.go +++ b/vendor/github.com/gonum/graph/path/bellman_ford_moore.go @@ -8,18 +8,18 @@ import "github.com/gonum/graph" // BellmanFordFrom returns a shortest-path tree for a shortest path from u to all nodes in // the graph g, or false indicating that a negative cycle exists in the graph. If the graph -// does not implement graph.Weighter, graph.UniformCost is used. +// does not implement graph.Weighter, UniformCost is used. // // The time complexity of BellmanFordFrom is O(|V|.|E|). func BellmanFordFrom(u graph.Node, g graph.Graph) (path Shortest, ok bool) { if !g.Has(u) { return Shortest{from: u}, true } - var weight graph.WeightFunc - if g, ok := g.(graph.Weighter); ok { - weight = g.Weight + var weight Weighting + if wg, ok := g.(graph.Weighter); ok { + weight = wg.Weight } else { - weight = graph.UniformCost + weight = UniformCost(g) } nodes := g.Nodes() @@ -34,7 +34,11 @@ func BellmanFordFrom(u graph.Node, g graph.Graph) (path Shortest, ok bool) { for j, u := range nodes { for _, v := range g.From(u) { k := path.indexOf[v.ID()] - joint := path.dist[j] + weight(g.Edge(u, v)) + w, ok := weight(u, v) + if !ok { + panic("bellman-ford: unexpected invalid weight") + } + joint := path.dist[j] + w if joint < path.dist[k] { path.set(k, joint, j) changed = true @@ -49,7 +53,11 @@ func BellmanFordFrom(u graph.Node, g graph.Graph) (path Shortest, ok bool) { for j, u := range nodes { for _, v := range g.From(u) { k := path.indexOf[v.ID()] - if path.dist[j]+weight(g.Edge(u, v)) < path.dist[k] { + w, ok := weight(u, v) + if !ok { + panic("bellman-ford: unexpected invalid weight") + } + if path.dist[j]+w < path.dist[k] { return path, false } } diff --git a/vendor/github.com/gonum/graph/path/bellman_ford_moore_test.go b/vendor/github.com/gonum/graph/path/bellman_ford_moore_test.go index 5d5934cf3d92..49be58d6f125 100644 --- a/vendor/github.com/gonum/graph/path/bellman_ford_moore_test.go +++ b/vendor/github.com/gonum/graph/path/bellman_ford_moore_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package path_test +package path import ( "math" @@ -10,47 +10,47 @@ import ( "testing" "github.com/gonum/graph" - "github.com/gonum/graph/path" + "github.com/gonum/graph/path/internal/testgraphs" ) func TestBellmanFordFrom(t *testing.T) { - for _, test := range shortestPathTests { - g := test.g() - for _, e := range test.edges { - g.SetEdge(e, e.Cost) + for _, test := range testgraphs.ShortestPathTests { + g := test.Graph() + for _, e := range test.Edges { + g.SetEdge(e) } - pt, ok := path.BellmanFordFrom(test.query.From(), g.(graph.Graph)) - if test.hasNegativeCycle { + pt, ok := BellmanFordFrom(test.Query.From(), g.(graph.Graph)) + if test.HasNegativeCycle { if ok { - t.Errorf("%q: expected negative cycle", test.name) + t.Errorf("%q: expected negative cycle", test.Name) } continue } if !ok { - t.Fatalf("%q: unexpected negative cycle", test.name) + t.Fatalf("%q: unexpected negative cycle", test.Name) } - if pt.From().ID() != test.query.From().ID() { - t.Fatalf("%q: unexpected from node ID: got:%d want:%d", pt.From().ID(), test.query.From().ID()) + if pt.From().ID() != test.Query.From().ID() { + t.Fatalf("%q: unexpected from node ID: got:%d want:%d", pt.From().ID(), test.Query.From().ID()) } - p, weight := pt.To(test.query.To()) - if weight != test.weight { + p, weight := pt.To(test.Query.To()) + if weight != test.Weight { t.Errorf("%q: unexpected weight from Between: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } - if weight := pt.WeightTo(test.query.To()); weight != test.weight { + if weight := pt.WeightTo(test.Query.To()); weight != test.Weight { t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } var got []int for _, n := range p { got = append(got, n.ID()) } - ok = len(got) == 0 && len(test.want) == 0 - for _, sp := range test.want { + ok = len(got) == 0 && len(test.WantPaths) == 0 + for _, sp := range test.WantPaths { if reflect.DeepEqual(got, sp) { ok = true break @@ -58,13 +58,13 @@ func TestBellmanFordFrom(t *testing.T) { } if !ok { t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", - test.name, p, test.want) + test.Name, p, test.WantPaths) } - np, weight := pt.To(test.none.To()) - if pt.From().ID() == test.none.From().ID() && (np != nil || !math.IsInf(weight, 1)) { + np, weight := pt.To(test.NoPathFor.To()) + if pt.From().ID() == test.NoPathFor.From().ID() && (np != nil || !math.IsInf(weight, 1)) { t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f\nwant:path= weight=+Inf", - test.name, np, weight) + test.Name, np, weight) } } } diff --git a/vendor/github.com/gonum/graph/path/bench_test.go b/vendor/github.com/gonum/graph/path/bench_test.go new file mode 100644 index 000000000000..103a47136b8c --- /dev/null +++ b/vendor/github.com/gonum/graph/path/bench_test.go @@ -0,0 +1,142 @@ +// 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 path + +import ( + "math" + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/graphs/gen" + "github.com/gonum/graph/simple" +) + +var ( + gnpUndirected_10_tenth = gnpUndirected(10, 0.1) + gnpUndirected_100_tenth = gnpUndirected(100, 0.1) + gnpUndirected_1000_tenth = gnpUndirected(1000, 0.1) + gnpUndirected_10_half = gnpUndirected(10, 0.5) + gnpUndirected_100_half = gnpUndirected(100, 0.5) + gnpUndirected_1000_half = gnpUndirected(1000, 0.5) +) + +func gnpUndirected(n int, p float64) graph.Undirected { + g := simple.NewUndirectedGraph(0, math.Inf(1)) + gen.Gnp(g, n, p, nil) + return g +} + +func benchmarkAStarNilHeuristic(b *testing.B, g graph.Undirected) { + var expanded int + for i := 0; i < b.N; i++ { + _, expanded = AStar(simple.Node(0), simple.Node(1), g, nil) + } + if expanded == 0 { + b.Fatal("unexpected number of expanded nodes") + } +} + +func BenchmarkAStarGnp_10_tenth(b *testing.B) { + benchmarkAStarNilHeuristic(b, gnpUndirected_10_tenth) +} +func BenchmarkAStarGnp_100_tenth(b *testing.B) { + benchmarkAStarNilHeuristic(b, gnpUndirected_100_tenth) +} +func BenchmarkAStarGnp_1000_tenth(b *testing.B) { + benchmarkAStarNilHeuristic(b, gnpUndirected_1000_tenth) +} +func BenchmarkAStarGnp_10_half(b *testing.B) { + benchmarkAStarNilHeuristic(b, gnpUndirected_10_half) +} +func BenchmarkAStarGnp_100_half(b *testing.B) { + benchmarkAStarNilHeuristic(b, gnpUndirected_100_half) +} +func BenchmarkAStarGnp_1000_half(b *testing.B) { + benchmarkAStarNilHeuristic(b, gnpUndirected_1000_half) +} + +var ( + nswUndirected_10_2_2_2 = navigableSmallWorldUndirected(10, 2, 2, 2) + nswUndirected_10_2_5_2 = navigableSmallWorldUndirected(10, 2, 5, 2) + nswUndirected_100_5_10_2 = navigableSmallWorldUndirected(100, 5, 10, 2) + nswUndirected_100_5_20_2 = navigableSmallWorldUndirected(100, 5, 20, 2) +) + +func navigableSmallWorldUndirected(n, p, q int, r float64) graph.Undirected { + g := simple.NewUndirectedGraph(0, math.Inf(1)) + gen.NavigableSmallWorld(g, []int{n, n}, p, q, r, nil) + return g +} + +func coordinatesForID(n graph.Node, c, r int) [2]int { + id := n.ID() + if id >= c*r { + panic("out of range") + } + return [2]int{id / r, id % r} +} + +// manhattanBetween returns the Manhattan distance between a and b. +func manhattanBetween(a, b [2]int) float64 { + var d int + for i, v := range a { + d += abs(v - b[i]) + } + return float64(d) +} + +func abs(a int) int { + if a < 0 { + return -a + } + return a +} + +func benchmarkAStarHeuristic(b *testing.B, g graph.Undirected, h Heuristic) { + var expanded int + for i := 0; i < b.N; i++ { + _, expanded = AStar(simple.Node(0), simple.Node(1), g, h) + } + if expanded == 0 { + b.Fatal("unexpected number of expanded nodes") + } +} + +func BenchmarkAStarUndirectedmallWorld_10_2_2_2(b *testing.B) { + benchmarkAStarHeuristic(b, nswUndirected_10_2_2_2, nil) +} +func BenchmarkAStarUndirectedmallWorld_10_2_2_2_Heur(b *testing.B) { + h := func(x, y graph.Node) float64 { + return manhattanBetween(coordinatesForID(x, 10, 10), coordinatesForID(y, 10, 10)) + } + benchmarkAStarHeuristic(b, nswUndirected_10_2_2_2, h) +} +func BenchmarkAStarUndirectedmallWorld_10_2_5_2(b *testing.B) { + benchmarkAStarHeuristic(b, nswUndirected_10_2_5_2, nil) +} +func BenchmarkAStarUndirectedmallWorld_10_2_5_2_Heur(b *testing.B) { + h := func(x, y graph.Node) float64 { + return manhattanBetween(coordinatesForID(x, 10, 10), coordinatesForID(y, 10, 10)) + } + benchmarkAStarHeuristic(b, nswUndirected_10_2_5_2, h) +} +func BenchmarkAStarUndirectedmallWorld_100_5_10_2(b *testing.B) { + benchmarkAStarHeuristic(b, nswUndirected_100_5_10_2, nil) +} +func BenchmarkAStarUndirectedmallWorld_100_5_10_2_Heur(b *testing.B) { + h := func(x, y graph.Node) float64 { + return manhattanBetween(coordinatesForID(x, 100, 100), coordinatesForID(y, 100, 100)) + } + benchmarkAStarHeuristic(b, nswUndirected_100_5_10_2, h) +} +func BenchmarkAStarUndirectedmallWorld_100_5_20_2(b *testing.B) { + benchmarkAStarHeuristic(b, nswUndirected_100_5_20_2, nil) +} +func BenchmarkAStarUndirectedmallWorld_100_5_20_2_Heur(b *testing.B) { + h := func(x, y graph.Node) float64 { + return manhattanBetween(coordinatesForID(x, 100, 100), coordinatesForID(y, 100, 100)) + } + benchmarkAStarHeuristic(b, nswUndirected_100_5_20_2, h) +} diff --git a/vendor/github.com/gonum/graph/path/control_flow.go b/vendor/github.com/gonum/graph/path/control_flow.go index 219226d5d7f0..81ee33d7c61d 100644 --- a/vendor/github.com/gonum/graph/path/control_flow.go +++ b/vendor/github.com/gonum/graph/path/control_flow.go @@ -6,17 +6,17 @@ package path import ( "github.com/gonum/graph" - "github.com/gonum/graph/internal" + "github.com/gonum/graph/internal/set" ) -// PostDominatores returns all dominators for all nodes in g. It does not +// Dominators returns all dominators for all nodes in g. It does not // prune for strict post-dominators, immediate dominators etc. // // A dominates B if and only if the only path through B travels through A. -func Dominators(start graph.Node, g graph.Graph) map[int]internal.Set { - allNodes := make(internal.Set) +func Dominators(start graph.Node, g graph.Graph) map[int]set.Nodes { + allNodes := make(set.Nodes) nlist := g.Nodes() - dominators := make(map[int]internal.Set, len(nlist)) + dominators := make(map[int]set.Nodes, len(nlist)) for _, node := range nlist { allNodes.Add(node) } @@ -30,7 +30,7 @@ func Dominators(start graph.Node, g graph.Graph) map[int]internal.Set { } for _, node := range nlist { - dominators[node.ID()] = make(internal.Set) + dominators[node.ID()] = make(set.Nodes) if node.ID() == start.ID() { dominators[node.ID()].Add(start) } else { @@ -48,16 +48,16 @@ func Dominators(start graph.Node, g graph.Graph) map[int]internal.Set { if len(preds) == 0 { continue } - tmp := make(internal.Set).Copy(dominators[preds[0].ID()]) + tmp := make(set.Nodes).Copy(dominators[preds[0].ID()]) for _, pred := range preds[1:] { tmp.Intersect(tmp, dominators[pred.ID()]) } - dom := make(internal.Set) + dom := make(set.Nodes) dom.Add(node) dom.Union(dom, tmp) - if !internal.Equal(dom, dominators[node.ID()]) { + if !set.Equal(dom, dominators[node.ID()]) { dominators[node.ID()] = dom somethingChanged = true } @@ -67,20 +67,20 @@ func Dominators(start graph.Node, g graph.Graph) map[int]internal.Set { return dominators } -// PostDominatores returns all post-dominators for all nodes in g. It does not +// PostDominators returns all post-dominators for all nodes in g. It does not // prune for strict post-dominators, immediate post-dominators etc. // // A post-dominates B if and only if all paths from B travel through A. -func PostDominators(end graph.Node, g graph.Graph) map[int]internal.Set { - allNodes := make(internal.Set) +func PostDominators(end graph.Node, g graph.Graph) map[int]set.Nodes { + allNodes := make(set.Nodes) nlist := g.Nodes() - dominators := make(map[int]internal.Set, len(nlist)) + dominators := make(map[int]set.Nodes, len(nlist)) for _, node := range nlist { allNodes.Add(node) } for _, node := range nlist { - dominators[node.ID()] = make(internal.Set) + dominators[node.ID()] = make(set.Nodes) if node.ID() == end.ID() { dominators[node.ID()].Add(end) } else { @@ -98,16 +98,16 @@ func PostDominators(end graph.Node, g graph.Graph) map[int]internal.Set { if len(succs) == 0 { continue } - tmp := make(internal.Set).Copy(dominators[succs[0].ID()]) + tmp := make(set.Nodes).Copy(dominators[succs[0].ID()]) for _, succ := range succs[1:] { tmp.Intersect(tmp, dominators[succ.ID()]) } - dom := make(internal.Set) + dom := make(set.Nodes) dom.Add(node) dom.Union(dom, tmp) - if !internal.Equal(dom, dominators[node.ID()]) { + if !set.Equal(dom, dominators[node.ID()]) { dominators[node.ID()] = dom somethingChanged = true } diff --git a/vendor/github.com/gonum/graph/path/dijkstra.go b/vendor/github.com/gonum/graph/path/dijkstra.go index 254676b3b18c..6eb97a9d0e20 100644 --- a/vendor/github.com/gonum/graph/path/dijkstra.go +++ b/vendor/github.com/gonum/graph/path/dijkstra.go @@ -11,19 +11,19 @@ import ( ) // DijkstraFrom returns a shortest-path tree for a shortest path from u to all nodes in -// the graph g. If the graph does not implement graph.Weighter, graph.UniformCost is used. +// the graph g. If the graph does not implement graph.Weighter, UniformCost is used. // DijkstraFrom will panic if g has a u-reachable negative edge weight. // -// The time complexity of DijkstrFrom is O(|E|+|V|.log|V|). +// The time complexity of DijkstrFrom is O(|E|.log|V|). func DijkstraFrom(u graph.Node, g graph.Graph) Shortest { if !g.Has(u) { return Shortest{from: u} } - var weight graph.WeightFunc - if g, ok := g.(graph.Weighter); ok { - weight = g.Weight + var weight Weighting + if wg, ok := g.(graph.Weighter); ok { + weight = wg.Weight } else { - weight = graph.UniformCost + weight = UniformCost(g) } nodes := g.Nodes() @@ -33,17 +33,25 @@ func DijkstraFrom(u graph.Node, g graph.Graph) Shortest { // described in Function B.2 in figure 6 of UTCS Technical // Report TR-07-54. // + // This implementation deviates from the report as follows: + // - the value of path.dist for the start vertex u is initialized to 0; + // - outdated elements from the priority queue (i.e. with respect to the dist value) + // are skipped. + // // http://www.cs.utexas.edu/ftp/techreports/tr07-54.pdf Q := priorityQueue{{node: u, dist: 0}} for Q.Len() != 0 { mid := heap.Pop(&Q).(distanceNode) k := path.indexOf[mid.node.ID()] - if mid.dist < path.dist[k] { - path.dist[k] = mid.dist + if mid.dist > path.dist[k] { + continue } for _, v := range g.From(mid.node) { j := path.indexOf[v.ID()] - w := weight(g.Edge(mid.node, v)) + w, ok := weight(mid.node, v) + if !ok { + panic("dijkstra: unexpected invalid weight") + } if w < 0 { panic("dijkstra: negative edge weight") } @@ -59,7 +67,7 @@ func DijkstraFrom(u graph.Node, g graph.Graph) Shortest { } // DijkstraAllPaths returns a shortest-path tree for shortest paths in the graph g. -// If the graph does not implement graph.Weighter, graph.UniformCost is used. +// If the graph does not implement graph.Weighter, UniformCost is used. // DijkstraAllPaths will panic if g has a negative edge weight. // // The time complexity of DijkstrAllPaths is O(|V|.|E|+|V|^2.log|V|). @@ -74,11 +82,11 @@ func DijkstraAllPaths(g graph.Graph) (paths AllShortest) { // of the nodes slice and the indexOf map. It returns nothing, but stores the // result of the work in the paths parameter which is a reference type. func dijkstraAllPaths(g graph.Graph, paths AllShortest) { - var weight graph.WeightFunc - if g, ok := g.(graph.Weighter); ok { - weight = g.Weight + var weight Weighting + if wg, ok := g.(graph.Weighter); ok { + weight = wg.Weight } else { - weight = graph.UniformCost + weight = UniformCost(g) } var Q priorityQueue @@ -100,7 +108,10 @@ func dijkstraAllPaths(g graph.Graph, paths AllShortest) { } for _, v := range g.From(mid.node) { j := paths.indexOf[v.ID()] - w := weight(g.Edge(mid.node, v)) + w, ok := weight(mid.node, v) + if !ok { + panic("dijkstra: unexpected invalid weight") + } if w < 0 { panic("dijkstra: negative edge weight") } diff --git a/vendor/github.com/gonum/graph/path/dijkstra_test.go b/vendor/github.com/gonum/graph/path/dijkstra_test.go index c22d28f5ef91..931a14116ad4 100644 --- a/vendor/github.com/gonum/graph/path/dijkstra_test.go +++ b/vendor/github.com/gonum/graph/path/dijkstra_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package path_test +package path import ( "math" @@ -11,19 +11,19 @@ import ( "testing" "github.com/gonum/graph" - "github.com/gonum/graph/internal" - "github.com/gonum/graph/path" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/graph/path/internal/testgraphs" ) func TestDijkstraFrom(t *testing.T) { - for _, test := range shortestPathTests { - g := test.g() - for _, e := range test.edges { - g.SetEdge(e, e.Cost) + for _, test := range testgraphs.ShortestPathTests { + g := test.Graph() + for _, e := range test.Edges { + g.SetEdge(e) } var ( - pt path.Shortest + pt Shortest panicked bool ) @@ -31,38 +31,38 @@ func TestDijkstraFrom(t *testing.T) { defer func() { panicked = recover() != nil }() - pt = path.DijkstraFrom(test.query.From(), g.(graph.Graph)) + pt = DijkstraFrom(test.Query.From(), g.(graph.Graph)) }() - if panicked || test.negative { - if !test.negative { - t.Errorf("%q: unexpected panic", test.name) + if panicked || test.HasNegativeWeight { + if !test.HasNegativeWeight { + t.Errorf("%q: unexpected panic", test.Name) } if !panicked { - t.Errorf("%q: expected panic for negative edge weight", test.name) + t.Errorf("%q: expected panic for negative edge weight", test.Name) } continue } - if pt.From().ID() != test.query.From().ID() { - t.Fatalf("%q: unexpected from node ID: got:%d want:%d", pt.From().ID(), test.query.From().ID()) + if pt.From().ID() != test.Query.From().ID() { + t.Fatalf("%q: unexpected from node ID: got:%d want:%d", pt.From().ID(), test.Query.From().ID()) } - p, weight := pt.To(test.query.To()) - if weight != test.weight { + p, weight := pt.To(test.Query.To()) + if weight != test.Weight { t.Errorf("%q: unexpected weight from Between: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } - if weight := pt.WeightTo(test.query.To()); weight != test.weight { + if weight := pt.WeightTo(test.Query.To()); weight != test.Weight { t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } var got []int for _, n := range p { got = append(got, n.ID()) } - ok := len(got) == 0 && len(test.want) == 0 - for _, sp := range test.want { + ok := len(got) == 0 && len(test.WantPaths) == 0 + for _, sp := range test.WantPaths { if reflect.DeepEqual(got, sp) { ok = true break @@ -70,26 +70,26 @@ func TestDijkstraFrom(t *testing.T) { } if !ok { t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", - test.name, p, test.want) + test.Name, p, test.WantPaths) } - np, weight := pt.To(test.none.To()) - if pt.From().ID() == test.none.From().ID() && (np != nil || !math.IsInf(weight, 1)) { + np, weight := pt.To(test.NoPathFor.To()) + if pt.From().ID() == test.NoPathFor.From().ID() && (np != nil || !math.IsInf(weight, 1)) { t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f\nwant:path= weight=+Inf", - test.name, np, weight) + test.Name, np, weight) } } } func TestDijkstraAllPaths(t *testing.T) { - for _, test := range shortestPathTests { - g := test.g() - for _, e := range test.edges { - g.SetEdge(e, e.Cost) + for _, test := range testgraphs.ShortestPathTests { + g := test.Graph() + for _, e := range test.Edges { + g.SetEdge(e) } var ( - pt path.AllShortest + pt AllShortest panicked bool ) @@ -97,40 +97,40 @@ func TestDijkstraAllPaths(t *testing.T) { defer func() { panicked = recover() != nil }() - pt = path.DijkstraAllPaths(g.(graph.Graph)) + pt = DijkstraAllPaths(g.(graph.Graph)) }() - if panicked || test.negative { - if !test.negative { - t.Errorf("%q: unexpected panic", test.name) + if panicked || test.HasNegativeWeight { + if !test.HasNegativeWeight { + t.Errorf("%q: unexpected panic", test.Name) } if !panicked { - t.Errorf("%q: expected panic for negative edge weight", test.name) + t.Errorf("%q: expected panic for negative edge weight", test.Name) } continue } // Check all random paths returned are OK. for i := 0; i < 10; i++ { - p, weight, unique := pt.Between(test.query.From(), test.query.To()) - if weight != test.weight { + p, weight, unique := pt.Between(test.Query.From(), test.Query.To()) + if weight != test.Weight { t.Errorf("%q: unexpected weight from Between: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } - if weight := pt.Weight(test.query.From(), test.query.To()); weight != test.weight { + if weight := pt.Weight(test.Query.From(), test.Query.To()); weight != test.Weight { t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } - if unique != test.unique { + if unique != test.HasUniquePath { t.Errorf("%q: unexpected number of paths: got: unique=%t want: unique=%t", - test.name, unique, test.unique) + test.Name, unique, test.HasUniquePath) } var got []int for _, n := range p { got = append(got, n.ID()) } - ok := len(got) == 0 && len(test.want) == 0 - for _, sp := range test.want { + ok := len(got) == 0 && len(test.WantPaths) == 0 + for _, sp := range test.WantPaths { if reflect.DeepEqual(got, sp) { ok = true break @@ -138,20 +138,20 @@ func TestDijkstraAllPaths(t *testing.T) { } if !ok { t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", - test.name, p, test.want) + test.Name, p, test.WantPaths) } } - np, weight, unique := pt.Between(test.none.From(), test.none.To()) + np, weight, unique := pt.Between(test.NoPathFor.From(), test.NoPathFor.To()) if np != nil || !math.IsInf(weight, 1) || unique != false { t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path= weight=+Inf unique=false", - test.name, np, weight, unique) + test.Name, np, weight, unique) } - paths, weight := pt.AllBetween(test.query.From(), test.query.To()) - if weight != test.weight { + paths, weight := pt.AllBetween(test.Query.From(), test.Query.To()) + if weight != test.Weight { t.Errorf("%q: unexpected weight from Between: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } var got [][]int @@ -163,16 +163,16 @@ func TestDijkstraAllPaths(t *testing.T) { got[i] = append(got[i], v.ID()) } } - sort.Sort(internal.BySliceValues(got)) - if !reflect.DeepEqual(got, test.want) { + sort.Sort(ordered.BySliceValues(got)) + if !reflect.DeepEqual(got, test.WantPaths) { t.Errorf("testing %q: unexpected shortest paths:\ngot: %v\nwant:%v", - test.name, got, test.want) + test.Name, got, test.WantPaths) } - nps, weight := pt.AllBetween(test.none.From(), test.none.To()) + nps, weight := pt.AllBetween(test.NoPathFor.From(), test.NoPathFor.To()) if nps != nil || !math.IsInf(weight, 1) { t.Errorf("%q: unexpected path:\ngot: paths=%v weight=%f\nwant:path= weight=+Inf", - test.name, nps, weight) + test.Name, nps, weight) } } } diff --git a/vendor/github.com/gonum/graph/path/disjoint.go b/vendor/github.com/gonum/graph/path/disjoint.go index 0755f695a9d3..1840d380e371 100644 --- a/vendor/github.com/gonum/graph/path/disjoint.go +++ b/vendor/github.com/gonum/graph/path/disjoint.go @@ -82,6 +82,6 @@ func (ds *disjointSet) union(x, y *disjointSetNode) { yRoot.parent = xRoot } else { yRoot.parent = xRoot - xRoot.rank += 1 + xRoot.rank++ } } diff --git a/vendor/github.com/gonum/graph/path/doc.go b/vendor/github.com/gonum/graph/path/doc.go new file mode 100644 index 000000000000..2a86fed04aa9 --- /dev/null +++ b/vendor/github.com/gonum/graph/path/doc.go @@ -0,0 +1,6 @@ +// Copyright ©2016 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 path provides graph path finding functions. +package path diff --git a/vendor/github.com/gonum/graph/path/dynamic/doc.go b/vendor/github.com/gonum/graph/path/dynamic/doc.go new file mode 100644 index 000000000000..597bd9567d66 --- /dev/null +++ b/vendor/github.com/gonum/graph/path/dynamic/doc.go @@ -0,0 +1,6 @@ +// Copyright ©2016 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 dynamic provides incremental heuristic graph path finding functions. +package dynamic diff --git a/vendor/github.com/gonum/graph/path/dynamic/dstarlite.go b/vendor/github.com/gonum/graph/path/dynamic/dstarlite.go new file mode 100644 index 000000000000..c7d7a9ccb5a7 --- /dev/null +++ b/vendor/github.com/gonum/graph/path/dynamic/dstarlite.go @@ -0,0 +1,491 @@ +// Copyright ©2014 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 dynamic + +import ( + "container/heap" + "fmt" + "math" + + "github.com/gonum/graph" + "github.com/gonum/graph/path" + "github.com/gonum/graph/simple" +) + +// DStarLite implements the D* Lite dynamic re-planning path search algorithm. +// +// doi:10.1109/tro.2004.838026 and ISBN:0-262-51129-0 pp476-483 +// +type DStarLite struct { + s, t *dStarLiteNode + last *dStarLiteNode + + model WorldModel + queue dStarLiteQueue + keyModifier float64 + + weight path.Weighting + heuristic path.Heuristic +} + +// WorldModel is a mutable weighted directed graph that returns nodes identified +// by id number. +type WorldModel interface { + graph.DirectedBuilder + graph.Weighter + Node(id int) graph.Node +} + +// NewDStarLite returns a new DStarLite planner for the path from s to t in g using the +// heuristic h. The world model, m, is used to store shortest path information during path +// planning. The world model must be an empty graph when NewDStarLite is called. +// +// If h is nil, the DStarLite will use the g.HeuristicCost method if g implements +// path.HeuristicCoster, falling back to path.NullHeuristic otherwise. If the graph does not +// implement graph.Weighter, path.UniformCost is used. NewDStarLite will panic if g has +// a negative edge weight. +func NewDStarLite(s, t graph.Node, g graph.Graph, h path.Heuristic, m WorldModel) *DStarLite { + /* + procedure Initialize() + {02”} U = ∅; + {03”} k_m = 0; + {04”} for all s ∈ S rhs(s) = g(s) = ∞; + {05”} rhs(s_goal) = 0; + {06”} U.Insert(s_goal, [h(s_start, s_goal); 0]); + */ + + d := &DStarLite{ + s: newDStarLiteNode(s), + t: newDStarLiteNode(t), // badKey is overwritten below. + + model: m, + + heuristic: h, + } + d.t.rhs = 0 + + /* + procedure Main() + {29”} s_last = s_start; + {30”} Initialize(); + */ + d.last = d.s + + if wg, ok := g.(graph.Weighter); ok { + d.weight = wg.Weight + } else { + d.weight = path.UniformCost(g) + } + if d.heuristic == nil { + if g, ok := g.(path.HeuristicCoster); ok { + d.heuristic = g.HeuristicCost + } else { + d.heuristic = path.NullHeuristic + } + } + + d.queue.insert(d.t, key{d.heuristic(s, t), 0}) + + for _, n := range g.Nodes() { + switch n.ID() { + case d.s.ID(): + d.model.AddNode(d.s) + case d.t.ID(): + d.model.AddNode(d.t) + default: + d.model.AddNode(newDStarLiteNode(n)) + } + } + for _, u := range d.model.Nodes() { + for _, v := range g.From(u) { + w := edgeWeight(d.weight, u, v) + if w < 0 { + panic("D* Lite: negative edge weight") + } + d.model.SetEdge(simple.Edge{F: u, T: d.model.Node(v.ID()), W: w}) + } + } + + /* + procedure Main() + {31”} ComputeShortestPath(); + */ + d.findShortestPath() + + return d +} + +// edgeWeight is a helper function that returns the weight of the edge between +// two connected nodes, u and v, using the provided weight function. It panics +// if there is no edge between u and v. +func edgeWeight(weight path.Weighting, u, v graph.Node) float64 { + w, ok := weight(u, v) + if !ok { + panic("D* Lite: unexpected invalid weight") + } + return w +} + +// keyFor is the CalculateKey procedure in the D* Lite papers. +func (d *DStarLite) keyFor(s *dStarLiteNode) key { + /* + procedure CalculateKey(s) + {01”} return [min(g(s), rhs(s)) + h(s_start, s) + k_m; min(g(s), rhs(s))]; + */ + k := key{1: math.Min(s.g, s.rhs)} + k[0] = k[1] + d.heuristic(d.s.Node, s.Node) + d.keyModifier + return k +} + +// update is the UpdateVertex procedure in the D* Lite papers. +func (d *DStarLite) update(u *dStarLiteNode) { + /* + procedure UpdateVertex(u) + {07”} if (g(u) != rhs(u) AND u ∈ U) U.Update(u,CalculateKey(u)); + {08”} else if (g(u) != rhs(u) AND u /∈ U) U.Insert(u,CalculateKey(u)); + {09”} else if (g(u) = rhs(u) AND u ∈ U) U.Remove(u); + */ + inQueue := u.inQueue() + switch { + case inQueue && u.g != u.rhs: + d.queue.update(u, d.keyFor(u)) + case !inQueue && u.g != u.rhs: + d.queue.insert(u, d.keyFor(u)) + case inQueue && u.g == u.rhs: + d.queue.remove(u) + } +} + +// findShortestPath is the ComputeShortestPath procedure in the D* Lite papers. +func (d *DStarLite) findShortestPath() { + /* + procedure ComputeShortestPath() + {10”} while (U.TopKey() < CalculateKey(s_start) OR rhs(s_start) > g(s_start)) + {11”} u = U.Top(); + {12”} k_old = U.TopKey(); + {13”} k_new = CalculateKey(u); + {14”} if(k_old < k_new) + {15”} U.Update(u, k_new); + {16”} else if (g(u) > rhs(u)) + {17”} g(u) = rhs(u); + {18”} U.Remove(u); + {19”} for all s ∈ Pred(u) + {20”} if (s != s_goal) rhs(s) = min(rhs(s), c(s, u) + g(u)); + {21”} UpdateVertex(s); + {22”} else + {23”} g_old = g(u); + {24”} g(u) = ∞; + {25”} for all s ∈ Pred(u) ∪ {u} + {26”} if (rhs(s) = c(s, u) + g_old) + {27”} if (s != s_goal) rhs(s) = min s'∈Succ(s)(c(s, s') + g(s')); + {28”} UpdateVertex(s); + */ + for d.queue.Len() != 0 { // We use d.queue.Len since d.queue does not return an infinite key when empty. + u := d.queue.top() + if !u.key.less(d.keyFor(d.s)) && d.s.rhs <= d.s.g { + break + } + switch kNew := d.keyFor(u); { + case u.key.less(kNew): + d.queue.update(u, kNew) + case u.g > u.rhs: + u.g = u.rhs + d.queue.remove(u) + for _, _s := range d.model.To(u) { + s := _s.(*dStarLiteNode) + if s.ID() != d.t.ID() { + s.rhs = math.Min(s.rhs, edgeWeight(d.model.Weight, s, u)+u.g) + } + d.update(s) + } + default: + gOld := u.g + u.g = math.Inf(1) + for _, _s := range append(d.model.To(u), u) { + s := _s.(*dStarLiteNode) + if s.rhs == edgeWeight(d.model.Weight, s, u)+gOld { + if s.ID() != d.t.ID() { + s.rhs = math.Inf(1) + for _, t := range d.model.From(s) { + s.rhs = math.Min(s.rhs, edgeWeight(d.model.Weight, s, t)+t.(*dStarLiteNode).g) + } + } + } + d.update(s) + } + } + } +} + +// Step performs one movement step along the best path towards the goal. +// It returns false if no further progression toward the goal can be +// achieved, either because the goal has been reached or because there +// is no path. +func (d *DStarLite) Step() bool { + /* + procedure Main() + {32”} while (s_start != s_goal) + {33”} // if (rhs(s_start) = ∞) then there is no known path + {34”} s_start = argmin s'∈Succ(s_start)(c(s_start, s') + g(s')); + */ + if d.s.ID() == d.t.ID() { + return false + } + if math.IsInf(d.s.rhs, 1) { + return false + } + + // We use rhs comparison to break ties + // between coequally weighted nodes. + rhs := math.Inf(1) + min := math.Inf(1) + + var next *dStarLiteNode + for _, _s := range d.model.From(d.s) { + s := _s.(*dStarLiteNode) + w := edgeWeight(d.model.Weight, d.s, s) + s.g + if w < min || (w == min && s.rhs < rhs) { + next = s + min = w + rhs = s.rhs + } + } + d.s = next + + /* + procedure Main() + {35”} Move to s_start; + */ + return true +} + +// MoveTo moves to n in the world graph. +func (d *DStarLite) MoveTo(n graph.Node) { + d.last = d.s + d.s = d.model.Node(n.ID()).(*dStarLiteNode) + d.keyModifier += d.heuristic(d.last, d.s) +} + +// UpdateWorld updates or adds edges in the world graph. UpdateWorld will +// panic if changes include a negative edge weight. +func (d *DStarLite) UpdateWorld(changes []graph.Edge) { + /* + procedure Main() + {36”} Scan graph for changed edge costs; + {37”} if any edge costs changed + {38”} k_m = k_m + h(s_last, s_start); + {39”} s_last = s_start; + {40”} for all directed edges (u, v) with changed edge costs + {41”} c_old = c(u, v); + {42”} Update the edge cost c(u, v); + {43”} if (c_old > c(u, v)) + {44”} if (u != s_goal) rhs(u) = min(rhs(u), c(u, v) + g(v)); + {45”} else if (rhs(u) = c_old + g(v)) + {46”} if (u != s_goal) rhs(u) = min s'∈Succ(u)(c(u, s') + g(s')); + {47”} UpdateVertex(u); + {48”} ComputeShortestPath() + */ + if len(changes) == 0 { + return + } + d.keyModifier += d.heuristic(d.last, d.s) + d.last = d.s + for _, e := range changes { + from := e.From() + to := e.To() + c, _ := d.weight(from, to) + if c < 0 { + panic("D* Lite: negative edge weight") + } + cOld, _ := d.model.Weight(from, to) + u := d.worldNodeFor(from) + v := d.worldNodeFor(to) + d.model.SetEdge(simple.Edge{F: u, T: v, W: c}) + if cOld > c { + if u.ID() != d.t.ID() { + u.rhs = math.Min(u.rhs, c+v.g) + } + } else if u.rhs == cOld+v.g { + if u.ID() != d.t.ID() { + u.rhs = math.Inf(1) + for _, t := range d.model.From(u) { + u.rhs = math.Min(u.rhs, edgeWeight(d.model.Weight, u, t)+t.(*dStarLiteNode).g) + } + } + } + d.update(u) + } + d.findShortestPath() +} + +func (d *DStarLite) worldNodeFor(n graph.Node) *dStarLiteNode { + switch w := d.model.Node(n.ID()).(type) { + case *dStarLiteNode: + return w + case graph.Node: + panic(fmt.Sprintf("D* Lite: illegal world model node type: %T", w)) + default: + return newDStarLiteNode(n) + } +} + +// Here returns the current location. +func (d *DStarLite) Here() graph.Node { + return d.s.Node +} + +// Path returns the path from the current location to the goal and the +// weight of the path. +func (d *DStarLite) Path() (p []graph.Node, weight float64) { + u := d.s + p = []graph.Node{u.Node} + for u.ID() != d.t.ID() { + if math.IsInf(u.rhs, 1) { + return nil, math.Inf(1) + } + + // We use stored rhs comparison to break + // ties between calculated rhs-coequal nodes. + rhsMin := math.Inf(1) + min := math.Inf(1) + var ( + next *dStarLiteNode + cost float64 + ) + for _, _v := range d.model.From(u) { + v := _v.(*dStarLiteNode) + w := edgeWeight(d.model.Weight, u, v) + if rhs := w + v.g; rhs < min || (rhs == min && v.rhs < rhsMin) { + next = v + min = rhs + rhsMin = v.rhs + cost = w + } + } + if next == nil { + return nil, math.NaN() + } + u = next + weight += cost + p = append(p, u.Node) + } + return p, weight +} + +/* +The pseudocode uses the following functions to manage the priority +queue: + + * U.Top() returns a vertex with the smallest priority of all + vertices in priority queue U. + * U.TopKey() returns the smallest priority of all vertices in + priority queue U. (If is empty, then U.TopKey() returns [∞;∞].) + * U.Pop() deletes the vertex with the smallest priority in + priority queue U and returns the vertex. + * U.Insert(s, k) inserts vertex s into priority queue with + priority k. + * U.Update(s, k) changes the priority of vertex s in priority + queue U to k. (It does nothing if the current priority of vertex + s already equals k.) + * Finally, U.Remove(s) removes vertex s from priority queue U. +*/ + +// key is a D* Lite priority queue key. +type key [2]float64 + +var badKey = key{math.NaN(), math.NaN()} + +// less returns whether k is less than other. From ISBN:0-262-51129-0 pp476-483: +// +// k ≤ k' iff k₁ < k'₁ OR (k₁ == k'₁ AND k₂ ≤ k'₂) +// +func (k key) less(other key) bool { + if k != k || other != other { + panic("D* Lite: poisoned key") + } + return k[0] < other[0] || (k[0] == other[0] && k[1] < other[1]) +} + +// dStarLiteNode adds D* Lite accounting to a graph.Node. +type dStarLiteNode struct { + graph.Node + key key + idx int + rhs float64 + g float64 +} + +// newDStarLiteNode returns a dStarLite node that is in a legal state +// for existence outside the DStarLite priority queue. +func newDStarLiteNode(n graph.Node) *dStarLiteNode { + return &dStarLiteNode{ + Node: n, + rhs: math.Inf(1), + g: math.Inf(1), + key: badKey, + idx: -1, + } +} + +// inQueue returns whether the node is in the queue. +func (q *dStarLiteNode) inQueue() bool { + return q.idx >= 0 +} + +// dStarLiteQueue is a D* Lite priority queue. +type dStarLiteQueue []*dStarLiteNode + +func (q dStarLiteQueue) Less(i, j int) bool { + return q[i].key.less(q[j].key) +} + +func (q dStarLiteQueue) Swap(i, j int) { + q[i], q[j] = q[j], q[i] + q[i].idx = i + q[j].idx = j +} + +func (q dStarLiteQueue) Len() int { + return len(q) +} + +func (q *dStarLiteQueue) Push(x interface{}) { + n := x.(*dStarLiteNode) + n.idx = len(*q) + *q = append(*q, n) +} + +func (q *dStarLiteQueue) Pop() interface{} { + n := (*q)[len(*q)-1] + n.idx = -1 + *q = (*q)[:len(*q)-1] + return n +} + +// top returns the top node in the queue. Note that instead of +// returning a key [∞;∞] when q is empty, the caller checks for +// an empty queue by calling q.Len. +func (q dStarLiteQueue) top() *dStarLiteNode { + return q[0] +} + +// insert puts the node u into the queue with the key k. +func (q *dStarLiteQueue) insert(u *dStarLiteNode, k key) { + u.key = k + heap.Push(q, u) +} + +// update updates the node in the queue identified by id with the key k. +func (q *dStarLiteQueue) update(n *dStarLiteNode, k key) { + n.key = k + heap.Fix(q, n.idx) +} + +// remove removes the node identified by id from the queue. +func (q *dStarLiteQueue) remove(n *dStarLiteNode) { + heap.Remove(q, n.idx) + n.key = badKey + n.idx = -1 +} diff --git a/vendor/github.com/gonum/graph/path/dynamic/dstarlite_test.go b/vendor/github.com/gonum/graph/path/dynamic/dstarlite_test.go new file mode 100644 index 000000000000..e6d433ef4a2b --- /dev/null +++ b/vendor/github.com/gonum/graph/path/dynamic/dstarlite_test.go @@ -0,0 +1,685 @@ +// 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 dynamic + +import ( + "bytes" + "flag" + "fmt" + "math" + "reflect" + "strings" + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/path" + "github.com/gonum/graph/path/internal" + "github.com/gonum/graph/path/internal/testgraphs" + "github.com/gonum/graph/simple" +) + +var ( + debug = flag.Bool("debug", false, "write path progress for failing dynamic case tests") + vdebug = flag.Bool("vdebug", false, "write path progress for all dynamic case tests (requires test.v)") + maxWide = flag.Int("maxwidth", 5, "maximum width grid to dump for debugging") +) + +func TestDStarLiteNullHeuristic(t *testing.T) { + for _, test := range testgraphs.ShortestPathTests { + // Skip zero-weight cycles. + if strings.HasPrefix(test.Name, "zero-weight") { + continue + } + + g := test.Graph() + for _, e := range test.Edges { + g.SetEdge(e) + } + + var ( + d *DStarLite + + panicked bool + ) + func() { + defer func() { + panicked = recover() != nil + }() + d = NewDStarLite(test.Query.From(), test.Query.To(), g.(graph.Graph), path.NullHeuristic, simple.NewDirectedGraph(0, math.Inf(1))) + }() + if panicked || test.HasNegativeWeight { + if !test.HasNegativeWeight { + t.Errorf("%q: unexpected panic", test.Name) + } + if !panicked { + t.Errorf("%q: expected panic for negative edge weight", test.Name) + } + continue + } + + p, weight := d.Path() + + if !math.IsInf(weight, 1) && p[0].ID() != test.Query.From().ID() { + t.Fatalf("%q: unexpected from node ID: got:%d want:%d", p[0].ID(), test.Query.From().ID()) + } + if weight != test.Weight { + t.Errorf("%q: unexpected weight from Between: got:%f want:%f", + test.Name, weight, test.Weight) + } + + var got []int + for _, n := range p { + got = append(got, n.ID()) + } + ok := len(got) == 0 && len(test.WantPaths) == 0 + for _, sp := range test.WantPaths { + if reflect.DeepEqual(got, sp) { + ok = true + break + } + } + if !ok { + t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", + test.Name, p, test.WantPaths) + } + } +} + +var dynamicDStarLiteTests = []struct { + g *internal.Grid + radius float64 + all bool + diag, unit bool + remember []bool + modify func(*internal.LimitedVisionGrid) + + heuristic func(dx, dy float64) float64 + + s, t graph.Node + + want []graph.Node + weight float64 + wantedPaths map[int][]graph.Node +}{ + { + // This is the example shown in figures 6 and 7 of doi:10.1109/tro.2004.838026. + g: internal.NewGridFrom( + "...", + ".*.", + ".*.", + ".*.", + "...", + ), + radius: 1.5, + all: true, + diag: true, + unit: true, + remember: []bool{false, true}, + + heuristic: func(dx, dy float64) float64 { + return math.Max(math.Abs(dx), math.Abs(dy)) + }, + + s: simple.Node(3), + t: simple.Node(14), + + want: []graph.Node{ + simple.Node(3), + simple.Node(6), + simple.Node(9), + simple.Node(13), + simple.Node(14), + }, + weight: 4, + }, + { + // This is a small example that has the property that the first corner + // may be taken incorrectly at 90° or correctly at 45° because the + // calculated rhs values of 12 and 17 are tied when moving from node + // 16, and the grid is small enough to examine by a dump. + g: internal.NewGridFrom( + ".....", + "...*.", + "**.*.", + "...*.", + ), + radius: 1.5, + all: true, + diag: true, + remember: []bool{false, true}, + + heuristic: func(dx, dy float64) float64 { + return math.Max(math.Abs(dx), math.Abs(dy)) + }, + + s: simple.Node(15), + t: simple.Node(14), + + want: []graph.Node{ + simple.Node(15), + simple.Node(16), + simple.Node(12), + simple.Node(7), + simple.Node(3), + simple.Node(9), + simple.Node(14), + }, + weight: 7.242640687119285, + wantedPaths: map[int][]graph.Node{ + 12: []graph.Node{simple.Node(12), simple.Node(7), simple.Node(3), simple.Node(9), simple.Node(14)}, + }, + }, + { + // This is the example shown in figure 2 of doi:10.1109/tro.2004.838026 + // with the exception that diagonal edge weights are calculated with the hypot + // function instead of a step count and only allowing information to be known + // from exploration. + g: internal.NewGridFrom( + "..................", + "..................", + "..................", + "..................", + "..................", + "..................", + "....*.*...........", + "*****.***.........", + "......*...........", + "......***.........", + "......*...........", + "......*...........", + "......*...........", + "*****.*...........", + "......*...........", + ), + radius: 1.5, + all: true, + diag: true, + remember: []bool{false, true}, + + heuristic: func(dx, dy float64) float64 { + return math.Max(math.Abs(dx), math.Abs(dy)) + }, + + s: simple.Node(253), + t: simple.Node(122), + + want: []graph.Node{ + simple.Node(253), + simple.Node(254), + simple.Node(255), + simple.Node(256), + simple.Node(239), + simple.Node(221), + simple.Node(203), + simple.Node(185), + simple.Node(167), + simple.Node(149), + simple.Node(131), + simple.Node(113), + simple.Node(96), + + // The following section depends + // on map iteration order. + nil, + nil, + nil, + nil, + nil, + nil, + nil, + + simple.Node(122), + }, + weight: 21.242640687119287, + }, + { + // This is the example shown in figure 2 of doi:10.1109/tro.2004.838026 + // with the exception that diagonal edge weights are calculated with the hypot + // function instead of a step count, not closing the exit and only allowing + // information to be known from exploration. + g: internal.NewGridFrom( + "..................", + "..................", + "..................", + "..................", + "..................", + "..................", + "....*.*...........", + "*****.***.........", + "..................", // Keep open. + "......***.........", + "......*...........", + "......*...........", + "......*...........", + "*****.*...........", + "......*...........", + ), + radius: 1.5, + all: true, + diag: true, + remember: []bool{false, true}, + + heuristic: func(dx, dy float64) float64 { + return math.Max(math.Abs(dx), math.Abs(dy)) + }, + + s: simple.Node(253), + t: simple.Node(122), + + want: []graph.Node{ + simple.Node(253), + simple.Node(254), + simple.Node(255), + simple.Node(256), + simple.Node(239), + simple.Node(221), + simple.Node(203), + simple.Node(185), + simple.Node(167), + simple.Node(150), + simple.Node(151), + simple.Node(152), + + // The following section depends + // on map iteration order. + nil, + nil, + nil, + nil, + nil, + + simple.Node(122), + }, + weight: 18.656854249492383, + }, + { + // This is the example shown in figure 2 of doi:10.1109/tro.2004.838026 + // with the exception that diagonal edge weights are calculated with the hypot + // function instead of a step count, the exit is closed at a distance and + // information is allowed to be known from exploration. + g: internal.NewGridFrom( + "..................", + "..................", + "..................", + "..................", + "..................", + "..................", + "....*.*...........", + "*****.***.........", + "........*.........", + "......***.........", + "......*...........", + "......*...........", + "......*...........", + "*****.*...........", + "......*...........", + ), + radius: 1.5, + all: true, + diag: true, + remember: []bool{false, true}, + + heuristic: func(dx, dy float64) float64 { + return math.Max(math.Abs(dx), math.Abs(dy)) + }, + + s: simple.Node(253), + t: simple.Node(122), + + want: []graph.Node{ + simple.Node(253), + simple.Node(254), + simple.Node(255), + simple.Node(256), + simple.Node(239), + simple.Node(221), + simple.Node(203), + simple.Node(185), + simple.Node(167), + simple.Node(150), + simple.Node(151), + simple.Node(150), + simple.Node(131), + simple.Node(113), + simple.Node(96), + + // The following section depends + // on map iteration order. + nil, + nil, + nil, + nil, + nil, + nil, + nil, + + simple.Node(122), + }, + weight: 24.07106781186548, + }, + { + // This is the example shown in figure 2 of doi:10.1109/tro.2004.838026 + // with the exception that diagonal edge weights are calculated with the hypot + // function instead of a step count. + g: internal.NewGridFrom( + "..................", + "..................", + "..................", + "..................", + "..................", + "..................", + "....*.*...........", + "*****.***.........", + "......*...........", // Forget this wall. + "......***.........", + "......*...........", + "......*...........", + "......*...........", + "*****.*...........", + "......*...........", + ), + radius: 1.5, + all: true, + diag: true, + remember: []bool{true}, + + modify: func(l *internal.LimitedVisionGrid) { + all := l.Grid.AllVisible + l.Grid.AllVisible = false + for _, n := range l.Nodes() { + l.Known[n.ID()] = !l.Grid.Has(n) + } + l.Grid.AllVisible = all + + const ( + wallRow = 8 + wallCol = 6 + ) + l.Known[l.NodeAt(wallRow, wallCol).ID()] = false + + // Check we have a correctly modified representation. + for _, u := range l.Nodes() { + for _, v := range l.Nodes() { + if l.HasEdgeBetween(u, v) != l.Grid.HasEdgeBetween(u, v) { + ur, uc := l.RowCol(u.ID()) + vr, vc := l.RowCol(v.ID()) + if (ur == wallRow && uc == wallCol) || (vr == wallRow && vc == wallCol) { + if !l.HasEdgeBetween(u, v) { + panic(fmt.Sprintf("expected to believe edge between %v (%d,%d) and %v (%d,%d) is passable", + u, v, ur, uc, vr, vc)) + } + continue + } + panic(fmt.Sprintf("disagreement about edge between %v (%d,%d) and %v (%d,%d): got:%t want:%t", + u, v, ur, uc, vr, vc, l.HasEdgeBetween(u, v), l.Grid.HasEdgeBetween(u, v))) + } + } + } + }, + + heuristic: func(dx, dy float64) float64 { + return math.Max(math.Abs(dx), math.Abs(dy)) + }, + + s: simple.Node(253), + t: simple.Node(122), + + want: []graph.Node{ + simple.Node(253), + simple.Node(254), + simple.Node(255), + simple.Node(256), + simple.Node(239), + simple.Node(221), + simple.Node(203), + simple.Node(185), + simple.Node(167), + simple.Node(149), + simple.Node(131), + simple.Node(113), + simple.Node(96), + + // The following section depends + // on map iteration order. + nil, + nil, + nil, + nil, + nil, + nil, + nil, + + simple.Node(122), + }, + weight: 21.242640687119287, + }, + { + g: internal.NewGridFrom( + "*..*", + "**.*", + "**.*", + "**.*", + ), + radius: 1, + all: true, + diag: false, + remember: []bool{false, true}, + + heuristic: func(dx, dy float64) float64 { + return math.Hypot(dx, dy) + }, + + s: simple.Node(1), + t: simple.Node(14), + + want: []graph.Node{ + simple.Node(1), + simple.Node(2), + simple.Node(6), + simple.Node(10), + simple.Node(14), + }, + weight: 4, + }, + { + g: internal.NewGridFrom( + "*..*", + "**.*", + "**.*", + "**.*", + ), + radius: 1.5, + all: true, + diag: true, + remember: []bool{false, true}, + + heuristic: func(dx, dy float64) float64 { + return math.Hypot(dx, dy) + }, + + s: simple.Node(1), + t: simple.Node(14), + + want: []graph.Node{ + simple.Node(1), + simple.Node(6), + simple.Node(10), + simple.Node(14), + }, + weight: math.Sqrt2 + 2, + }, + { + g: internal.NewGridFrom( + "...", + ".*.", + ".*.", + ".*.", + ".*.", + ), + radius: 1, + all: true, + diag: false, + remember: []bool{false, true}, + + heuristic: func(dx, dy float64) float64 { + return math.Hypot(dx, dy) + }, + + s: simple.Node(6), + t: simple.Node(14), + + want: []graph.Node{ + simple.Node(6), + simple.Node(9), + simple.Node(12), + simple.Node(9), + simple.Node(6), + simple.Node(3), + simple.Node(0), + simple.Node(1), + simple.Node(2), + simple.Node(5), + simple.Node(8), + simple.Node(11), + simple.Node(14), + }, + weight: 12, + }, +} + +func TestDStarLiteDynamic(t *testing.T) { + for i, test := range dynamicDStarLiteTests { + for _, remember := range test.remember { + l := &internal.LimitedVisionGrid{ + Grid: test.g, + VisionRadius: test.radius, + Location: test.s, + } + if remember { + l.Known = make(map[int]bool) + } + + l.Grid.AllVisible = test.all + + l.Grid.AllowDiagonal = test.diag + l.Grid.UnitEdgeWeight = test.unit + + if test.modify != nil { + test.modify(l) + } + + got := []graph.Node{test.s} + l.MoveTo(test.s) + + heuristic := func(a, b graph.Node) float64 { + ax, ay := l.XY(a) + bx, by := l.XY(b) + return test.heuristic(ax-bx, ay-by) + } + + world := simple.NewDirectedGraph(0, math.Inf(1)) + d := NewDStarLite(test.s, test.t, l, heuristic, world) + var ( + dp *dumper + buf bytes.Buffer + ) + _, c := l.Grid.Dims() + if c <= *maxWide && (*debug || *vdebug) { + dp = &dumper{ + w: &buf, + + dStarLite: d, + grid: l, + } + } + + dp.dump(true) + dp.printEdges("Initial world knowledge: %s\n\n", simpleEdgesOf(l, world.Edges())) + for d.Step() { + changes, _ := l.MoveTo(d.Here()) + got = append(got, l.Location) + d.UpdateWorld(changes) + dp.dump(true) + if wantedPath, ok := test.wantedPaths[l.Location.ID()]; ok { + gotPath, _ := d.Path() + if !samePath(gotPath, wantedPath) { + t.Errorf("unexpected intermediate path estimation for test %d %s memory:\ngot: %v\nwant:%v", + i, memory(remember), gotPath, wantedPath) + } + } + dp.printEdges("Edges changing after last step:\n%s\n\n", simpleEdgesOf(l, changes)) + } + + if weight := weightOf(got, l.Grid); !samePath(got, test.want) || weight != test.weight { + t.Errorf("unexpected path for test %d %s memory got weight:%v want weight:%v:\ngot: %v\nwant:%v", + i, memory(remember), weight, test.weight, got, test.want) + b, err := l.Render(got) + t.Errorf("path taken (err:%v):\n%s", err, b) + if c <= *maxWide && (*debug || *vdebug) { + t.Error(buf.String()) + } + } else if c <= *maxWide && *vdebug { + t.Logf("Test %d:\n%s", i, buf.String()) + } + } + } +} + +type memory bool + +func (m memory) String() string { + if m { + return "with" + } + return "without" +} + +// samePath compares two paths for equality ignoring nodes that are nil. +func samePath(a, b []graph.Node) bool { + if len(a) != len(b) { + return false + } + for i, e := range a { + if e == nil || b[i] == nil { + continue + } + if e.ID() != b[i].ID() { + return false + } + } + return true +} + +type weightedGraph interface { + graph.Graph + graph.Weighter +} + +// weightOf return the weight of the path in g. +func weightOf(path []graph.Node, g weightedGraph) float64 { + var w float64 + if len(path) > 1 { + for p, n := range path[1:] { + ew, ok := g.Weight(path[p], n) + if !ok { + return math.Inf(1) + } + w += ew + } + } + return w +} + +// simpleEdgesOf returns the weighted edges in g corresponding to the given edges. +func simpleEdgesOf(g weightedGraph, edges []graph.Edge) []simple.Edge { + w := make([]simple.Edge, len(edges)) + for i, e := range edges { + w[i].F = e.From() + w[i].T = e.To() + ew, _ := g.Weight(e.From(), e.To()) + w[i].W = ew + } + return w +} diff --git a/vendor/github.com/gonum/graph/path/dynamic/dumper_test.go b/vendor/github.com/gonum/graph/path/dynamic/dumper_test.go new file mode 100644 index 000000000000..7c4c0f87a28f --- /dev/null +++ b/vendor/github.com/gonum/graph/path/dynamic/dumper_test.go @@ -0,0 +1,153 @@ +// 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 dynamic + +import ( + "bytes" + "fmt" + "io" + "sort" + "text/tabwriter" + + "github.com/gonum/graph/path/internal" + "github.com/gonum/graph/simple" +) + +// dumper implements a grid D* Lite statistics dump. +type dumper struct { + step int + + dStarLite *DStarLite + grid *internal.LimitedVisionGrid + + w io.Writer +} + +// dump writes a single step of a D* Lite path search to the dumper's io.Writer. +func (d *dumper) dump(withpath bool) { + if d == nil { + return + } + var pathStep map[int]int + if withpath { + pathStep = make(map[int]int) + path, _ := d.dStarLite.Path() + for i, n := range path { + pathStep[n.ID()] = i + } + } + fmt.Fprintf(d.w, "Step:%d kₘ=%v\n", d.step, d.dStarLite.keyModifier) + d.step++ + w := tabwriter.NewWriter(d.w, 0, 0, 0, ' ', tabwriter.Debug) + rows, cols := d.grid.Grid.Dims() + for r := 0; r < rows; r++ { + if r == 0 { + for c := 0; c < cols; c++ { + if c != 0 { + fmt.Fprint(w, "\t") + } + fmt.Fprint(w, "-------------------") + } + fmt.Fprintln(w) + } + for ln := 0; ln < 6; ln++ { + for c := 0; c < cols; c++ { + if c != 0 { + fmt.Fprint(w, "\t") + } + n := d.dStarLite.model.Node(d.grid.NodeAt(r, c).ID()).(*dStarLiteNode) + switch ln { + case 0: + if n.ID() == d.grid.Location.ID() { + if d.grid.Grid.HasOpen(n) { + fmt.Fprintf(w, "id:%2d >@<", n.ID()) + } else { + // Mark location as illegal. + fmt.Fprintf(w, "id:%2d >!<", n.ID()) + } + } else if n.ID() == d.dStarLite.t.ID() { + fmt.Fprintf(w, "id:%2d G", n.ID()) + // Mark goal cell as illegal. + if !d.grid.Grid.HasOpen(n) { + fmt.Fprint(w, "!") + } + } else if pathStep[n.ID()] > 0 { + fmt.Fprintf(w, "id:%2d %2d", n.ID(), pathStep[n.ID()]) + // Mark path cells with an obstruction. + if !d.grid.Grid.HasOpen(n) { + fmt.Fprint(w, "!") + } + } else { + fmt.Fprintf(w, "id:%2d", n.ID()) + // Mark cells with an obstruction. + if !d.grid.Grid.HasOpen(n) { + fmt.Fprint(w, " *") + } + } + case 1: + fmt.Fprintf(w, "h: %.4v", d.dStarLite.heuristic(n, d.dStarLite.Here())) + case 2: + fmt.Fprintf(w, "g: %.4v", n.g) + case 3: + fmt.Fprintf(w, "rhs:%.4v", n.rhs) + case 4: + if n.g != n.rhs { + fmt.Fprintf(w, "key:%.3f", n.key) + } + if n.key == n.key { + // Mark keys for nodes in the priority queue. + // We use NaN inequality for this check since all + // keys not in the queue must have their key set + // to badKey. + // + // This should always mark cells where key is + // printed. + fmt.Fprint(w, "*") + } + if n.g > n.rhs { + fmt.Fprint(w, "^") + } + if n.g < n.rhs { + fmt.Fprint(w, "v") + } + default: + fmt.Fprint(w, "-------------------") + } + } + fmt.Fprintln(w) + } + } + w.Flush() + fmt.Fprintln(d.w) +} + +// printEdges pretty prints the given edges to the dumper's io.Writer using the provided +// format string. The edges are first formated to a string, so the format string must use +// the %s verb to indicate where the edges are to be printed. +func (d *dumper) printEdges(format string, edges []simple.Edge) { + if d == nil { + return + } + var buf bytes.Buffer + sort.Sort(lexically(edges)) + for i, e := range edges { + if i != 0 { + fmt.Fprint(&buf, ", ") + } + fmt.Fprintf(&buf, "%d->%d:%.4v", e.From().ID(), e.To().ID(), e.Weight()) + } + if len(edges) == 0 { + fmt.Fprint(&buf, "none") + } + fmt.Fprintf(d.w, format, buf.Bytes()) +} + +type lexically []simple.Edge + +func (l lexically) Len() int { return len(l) } +func (l lexically) Less(i, j int) bool { + return l[i].From().ID() < l[j].From().ID() || (l[i].From().ID() == l[j].From().ID() && l[i].To().ID() < l[j].To().ID()) +} +func (l lexically) Swap(i, j int) { l[i], l[j] = l[j], l[i] } diff --git a/vendor/github.com/gonum/graph/path/floydwarshall.go b/vendor/github.com/gonum/graph/path/floydwarshall.go index d4246835d8ad..bf5f237ac2a7 100644 --- a/vendor/github.com/gonum/graph/path/floydwarshall.go +++ b/vendor/github.com/gonum/graph/path/floydwarshall.go @@ -8,15 +8,15 @@ import "github.com/gonum/graph" // FloydWarshall returns a shortest-path tree for the graph g or false indicating // that a negative cycle exists in the graph. If the graph does not implement -// graph.Weighter, graph.UniformCost is used. +// graph.Weighter, UniformCost is used. // // The time complexity of FloydWarshall is O(|V|^3). func FloydWarshall(g graph.Graph) (paths AllShortest, ok bool) { - var weight graph.WeightFunc - if g, ok := g.(graph.Weighter); ok { - weight = g.Weight + var weight Weighting + if wg, ok := g.(graph.Weighter); ok { + weight = wg.Weight } else { - weight = graph.UniformCost + weight = UniformCost(g) } nodes := g.Nodes() @@ -25,7 +25,11 @@ func FloydWarshall(g graph.Graph) (paths AllShortest, ok bool) { paths.dist.Set(i, i, 0) for _, v := range g.From(u) { j := paths.indexOf[v.ID()] - paths.set(i, j, weight(g.Edge(u, v)), j) + w, ok := weight(u, v) + if !ok { + panic("floyd-warshall: unexpected invalid weight") + } + paths.set(i, j, w, j) } } diff --git a/vendor/github.com/gonum/graph/path/floydwarshall_test.go b/vendor/github.com/gonum/graph/path/floydwarshall_test.go index ea78e799c7a4..5cc1851af4e1 100644 --- a/vendor/github.com/gonum/graph/path/floydwarshall_test.go +++ b/vendor/github.com/gonum/graph/path/floydwarshall_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package path_test +package path import ( "math" @@ -11,50 +11,50 @@ import ( "testing" "github.com/gonum/graph" - "github.com/gonum/graph/internal" - "github.com/gonum/graph/path" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/graph/path/internal/testgraphs" ) func TestFloydWarshall(t *testing.T) { - for _, test := range shortestPathTests { - g := test.g() - for _, e := range test.edges { - g.SetEdge(e, e.Cost) + for _, test := range testgraphs.ShortestPathTests { + g := test.Graph() + for _, e := range test.Edges { + g.SetEdge(e) } - pt, ok := path.FloydWarshall(g.(graph.Graph)) - if test.hasNegativeCycle { + pt, ok := FloydWarshall(g.(graph.Graph)) + if test.HasNegativeCycle { if ok { - t.Errorf("%q: expected negative cycle", test.name) + t.Errorf("%q: expected negative cycle", test.Name) } continue } if !ok { - t.Fatalf("%q: unexpected negative cycle", test.name) + t.Fatalf("%q: unexpected negative cycle", test.Name) } // Check all random paths returned are OK. for i := 0; i < 10; i++ { - p, weight, unique := pt.Between(test.query.From(), test.query.To()) - if weight != test.weight { + p, weight, unique := pt.Between(test.Query.From(), test.Query.To()) + if weight != test.Weight { t.Errorf("%q: unexpected weight from Between: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } - if weight := pt.Weight(test.query.From(), test.query.To()); weight != test.weight { + if weight := pt.Weight(test.Query.From(), test.Query.To()); weight != test.Weight { t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } - if unique != test.unique { + if unique != test.HasUniquePath { t.Errorf("%q: unexpected number of paths: got: unique=%t want: unique=%t", - test.name, unique, test.unique) + test.Name, unique, test.HasUniquePath) } var got []int for _, n := range p { got = append(got, n.ID()) } - ok := len(got) == 0 && len(test.want) == 0 - for _, sp := range test.want { + ok := len(got) == 0 && len(test.WantPaths) == 0 + for _, sp := range test.WantPaths { if reflect.DeepEqual(got, sp) { ok = true break @@ -62,20 +62,20 @@ func TestFloydWarshall(t *testing.T) { } if !ok { t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", - test.name, p, test.want) + test.Name, p, test.WantPaths) } } - np, weight, unique := pt.Between(test.none.From(), test.none.To()) + np, weight, unique := pt.Between(test.NoPathFor.From(), test.NoPathFor.To()) if np != nil || !math.IsInf(weight, 1) || unique != false { t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path= weight=+Inf unique=false", - test.name, np, weight, unique) + test.Name, np, weight, unique) } - paths, weight := pt.AllBetween(test.query.From(), test.query.To()) - if weight != test.weight { + paths, weight := pt.AllBetween(test.Query.From(), test.Query.To()) + if weight != test.Weight { t.Errorf("%q: unexpected weight from Between: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } var got [][]int @@ -87,16 +87,16 @@ func TestFloydWarshall(t *testing.T) { got[i] = append(got[i], v.ID()) } } - sort.Sort(internal.BySliceValues(got)) - if !reflect.DeepEqual(got, test.want) { + sort.Sort(ordered.BySliceValues(got)) + if !reflect.DeepEqual(got, test.WantPaths) { t.Errorf("testing %q: unexpected shortest paths:\ngot: %v\nwant:%v", - test.name, got, test.want) + test.Name, got, test.WantPaths) } - nps, weight := pt.AllBetween(test.none.From(), test.none.To()) + nps, weight := pt.AllBetween(test.NoPathFor.From(), test.NoPathFor.To()) if nps != nil || !math.IsInf(weight, 1) { t.Errorf("%q: unexpected path:\ngot: paths=%v weight=%f\nwant:path= weight=+Inf", - test.name, nps, weight) + test.Name, nps, weight) } } } diff --git a/vendor/github.com/gonum/graph/path/internal/grid.go b/vendor/github.com/gonum/graph/path/internal/grid.go index 47190e598b74..8122ebf36dad 100644 --- a/vendor/github.com/gonum/graph/path/internal/grid.go +++ b/vendor/github.com/gonum/graph/path/internal/grid.go @@ -10,12 +10,13 @@ import ( "math" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" ) const ( - Closed = '*' // Closed is the closed grid node representation. - Open = '.' // Open is the open grid node repesentation. + Closed = '*' // Closed is the closed grid node representation. + Open = '.' // Open is the open grid node repesentation. + Unknown = '?' // Unknown is the unknown grid node repesentation. ) // Grid is a 2D grid planar undirected graph. @@ -24,6 +25,17 @@ type Grid struct { // diagonally adjacent nodes can // be connected by an edge. AllowDiagonal bool + // UnitEdgeWeight specifies whether + // finite edge weights are returned as + // the unit length. Otherwise edge + // weights are the Euclidean distance + // between connected nodes. + UnitEdgeWeight bool + + // AllVisible specifies whether + // non-open nodes are visible + // in calls to Nodes and HasNode. + AllVisible bool open []bool r, c int @@ -77,20 +89,31 @@ func NewGridFrom(rows ...string) *Grid { } } -// Nodes returns all the open nodes in the grid. +// Nodes returns all the open nodes in the grid if AllVisible is +// false, otherwise all nodes are returned. func (g *Grid) Nodes() []graph.Node { var nodes []graph.Node for id, ok := range g.open { - if !ok { - continue + if ok || g.AllVisible { + nodes = append(nodes, simple.Node(id)) } - nodes = append(nodes, concrete.Node(id)) } return nodes } -// Has returns whether n is an open node in the grid. +// Has returns whether n is a node in the grid. The state of +// the AllVisible field determines whether a non-open node is +// present. func (g *Grid) Has(n graph.Node) bool { + return g.has(n.ID()) +} + +func (g *Grid) has(id int) bool { + return id >= 0 && id < len(g.open) && (g.AllVisible || g.open[id]) +} + +// HasOpen returns whether n is an open node in the grid. +func (g *Grid) HasOpen(n graph.Node) bool { id := n.ID() return id >= 0 && id < len(g.open) && g.open[id] } @@ -135,19 +158,20 @@ func (g *Grid) NodeAt(r, c int) graph.Node { if r < 0 || r >= g.r || c < 0 || c >= g.c { return nil } - return concrete.Node(r*g.c + c) + return simple.Node(r*g.c + c) } -// From returns all the nodes reachable from u. +// From returns all the nodes reachable from u. Reachabilty requires that both +// ends of an edge must be open. func (g *Grid) From(u graph.Node) []graph.Node { - if !g.Has(u) { + if !g.HasOpen(u) { return nil } nr, nc := g.RowCol(u.ID()) var to []graph.Node for r := nr - 1; r <= nr+1; r++ { for c := nc - 1; c <= nc+1; c++ { - if v := g.NodeAt(r, c); v != nil && g.HasEdge(u, v) { + if v := g.NodeAt(r, c); v != nil && g.HasEdgeBetween(u, v) { to = append(to, v) } } @@ -155,14 +179,14 @@ func (g *Grid) From(u graph.Node) []graph.Node { return to } -// HasEdge returns whether there is an edge between u and v. -func (g *Grid) HasEdge(u, v graph.Node) bool { - if !g.Has(u) || !g.Has(v) || u.ID() == v.ID() { +// HasEdgeBetween returns whether there is an edge between u and v. +func (g *Grid) HasEdgeBetween(u, v graph.Node) bool { + if !g.HasOpen(u) || !g.HasOpen(v) || u.ID() == v.ID() { return false } ur, uc := g.RowCol(u.ID()) vr, vc := g.RowCol(v.ID()) - if abs(ur-vr) > 1 && abs(uc-vc) > 1 { + if abs(ur-vr) > 1 || abs(uc-vc) > 1 { return false } return g.AllowDiagonal || ur == vr || uc == vc @@ -182,24 +206,34 @@ func (g *Grid) Edge(u, v graph.Node) graph.Edge { // EdgeBetween returns the edge between u and v. func (g *Grid) EdgeBetween(u, v graph.Node) graph.Edge { - if g.HasEdge(u, v) { - return concrete.Edge{u, v} + if g.HasEdgeBetween(u, v) { + if !g.AllowDiagonal || g.UnitEdgeWeight { + return simple.Edge{F: u, T: v, W: 1} + } + ux, uy := g.XY(u) + vx, vy := g.XY(v) + return simple.Edge{F: u, T: v, W: math.Hypot(ux-vx, uy-vy)} } return nil } // Weight returns the weight of the given edge. -func (g *Grid) Weight(e graph.Edge) float64 { - if e := g.EdgeBetween(e.From(), e.To()); e != nil { - if !g.AllowDiagonal { - return 1 +func (g *Grid) Weight(x, y graph.Node) (w float64, ok bool) { + if x.ID() == y.ID() { + return 0, true + } + if !g.HasEdgeBetween(x, y) { + return math.Inf(1), false + } + if e := g.EdgeBetween(x, y); e != nil { + if !g.AllowDiagonal || g.UnitEdgeWeight { + return 1, true } ux, uy := g.XY(e.From()) vx, vy := g.XY(e.To()) - return math.Hypot(ux-vx, uy-vy) - + return math.Hypot(ux-vx, uy-vy), true } - return math.Inf(1) + return math.Inf(1), true } // String returns a string representation of the grid. @@ -230,7 +264,7 @@ func (g *Grid) Render(path []graph.Node) ([]byte, error) { // We don't use topo.IsPathIn at the outset because we // want to draw as much as possible before failing. for i, n := range path { - if !g.Has(n) || (i != 0 && !g.HasEdge(path[i-1], n)) { + if !g.Has(n) || (i != 0 && !g.HasEdgeBetween(path[i-1], n)) { id := n.ID() if id >= 0 && id < len(g.open) { r, c := g.RowCol(n.ID()) diff --git a/vendor/github.com/gonum/graph/path/internal/grid_test.go b/vendor/github.com/gonum/graph/path/internal/grid_test.go index 84c43d02afbf..d046a5334d94 100644 --- a/vendor/github.com/gonum/graph/path/internal/grid_test.go +++ b/vendor/github.com/gonum/graph/path/internal/grid_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package internal_test +package internal import ( "bytes" @@ -12,11 +12,10 @@ import ( "testing" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" - "github.com/gonum/graph/path/internal" + "github.com/gonum/graph/simple" ) -var _ graph.Graph = (*internal.Grid)(nil) +var _ graph.Graph = (*Grid)(nil) func join(g ...string) string { return strings.Join(g, "\n") } @@ -25,7 +24,7 @@ type node int func (n node) ID() int { return int(n) } func TestGrid(t *testing.T) { - g := internal.NewGrid(4, 4, false) + g := NewGrid(4, 4, false) got := g.String() want := join( @@ -125,7 +124,7 @@ func TestGrid(t *testing.T) { // Match the last state from the loop against the // explicit description of the grid. - got = internal.NewGridFrom( + got = NewGridFrom( "*..*", "**.*", "**.*", @@ -235,17 +234,17 @@ func TestGrid(t *testing.T) { { from: node(2), diagonal: false, - to: []graph.Node{concrete.Node(1), concrete.Node(6)}, + to: []graph.Node{simple.Node(1), simple.Node(6)}, }, { from: node(1), diagonal: false, - to: []graph.Node{concrete.Node(2)}, + to: []graph.Node{simple.Node(2)}, }, { from: node(1), diagonal: true, - to: []graph.Node{concrete.Node(2), concrete.Node(6)}, + to: []graph.Node{simple.Node(2), simple.Node(6)}, }, } for _, test := range reach { diff --git a/vendor/github.com/gonum/graph/path/internal/limited.go b/vendor/github.com/gonum/graph/path/internal/limited.go new file mode 100644 index 000000000000..7b900fde5577 --- /dev/null +++ b/vendor/github.com/gonum/graph/path/internal/limited.go @@ -0,0 +1,306 @@ +// 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 internal + +import ( + "errors" + "math" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" +) + +// LimitedVisionGrid is a 2D grid planar undirected graph where the capacity +// to determine the presence of edges is dependent on the current and past +// positions on the grid. In the absence of information, the grid is +// optimistic. +type LimitedVisionGrid struct { + Grid *Grid + + // Location is the current + // location on the grid. + Location graph.Node + + // VisionRadius specifies how far + // away edges can be detected. + VisionRadius float64 + + // Known holds a store of known + // nodes, if not nil. + Known map[int]bool +} + +// MoveTo moves to the node n on the grid and returns a slice of newly seen and +// already known edges. MoveTo panics if n is nil. +func (l *LimitedVisionGrid) MoveTo(n graph.Node) (new, old []graph.Edge) { + l.Location = n + row, column := l.RowCol(n.ID()) + x := float64(column) + y := float64(row) + seen := make(map[[2]int]bool) + bound := int(l.VisionRadius + 0.5) + for r := row - bound; r <= row+bound; r++ { + for c := column - bound; c <= column+bound; c++ { + u := l.NodeAt(r, c) + if u == nil { + continue + } + ux, uy := l.XY(u) + if math.Hypot(x-ux, y-uy) > l.VisionRadius { + continue + } + for _, v := range l.allPossibleFrom(u) { + if seen[[2]int{u.ID(), v.ID()}] { + continue + } + seen[[2]int{u.ID(), v.ID()}] = true + + vx, vy := l.XY(v) + if !l.Known[v.ID()] && math.Hypot(x-vx, y-vy) > l.VisionRadius { + continue + } + + e := simple.Edge{F: u, T: v} + if !l.Known[u.ID()] || !l.Known[v.ID()] { + new = append(new, e) + } else { + old = append(old, e) + } + } + } + } + + if l.Known != nil { + for r := row - bound; r <= row+bound; r++ { + for c := column - bound; c <= column+bound; c++ { + u := l.NodeAt(r, c) + if u == nil { + continue + } + ux, uy := l.XY(u) + if math.Hypot(x-ux, y-uy) > l.VisionRadius { + continue + } + for _, v := range l.allPossibleFrom(u) { + vx, vy := l.XY(v) + if math.Hypot(x-vx, y-vy) > l.VisionRadius { + continue + } + l.Known[v.ID()] = true + } + l.Known[u.ID()] = true + } + } + + } + + return new, old +} + +// allPossibleFrom returns all the nodes possibly reachable from u. +func (l *LimitedVisionGrid) allPossibleFrom(u graph.Node) []graph.Node { + if !l.Has(u) { + return nil + } + nr, nc := l.RowCol(u.ID()) + var to []graph.Node + for r := nr - 1; r <= nr+1; r++ { + for c := nc - 1; c <= nc+1; c++ { + v := l.NodeAt(r, c) + if v == nil || u.ID() == v.ID() { + continue + } + ur, uc := l.RowCol(u.ID()) + vr, vc := l.RowCol(v.ID()) + if abs(ur-vr) > 1 || abs(uc-vc) > 1 { + continue + } + if !l.Grid.AllowDiagonal && ur != vr && uc != vc { + continue + } + to = append(to, v) + } + } + return to +} + +// RowCol returns the row and column of the id. RowCol will panic if the +// node id is outside the range of the grid. +func (l *LimitedVisionGrid) RowCol(id int) (r, c int) { + return l.Grid.RowCol(id) +} + +// XY returns the cartesian coordinates of n. If n is not a node +// in the grid, (NaN, NaN) is returned. +func (l *LimitedVisionGrid) XY(n graph.Node) (x, y float64) { + if !l.Has(n) { + return math.NaN(), math.NaN() + } + r, c := l.RowCol(n.ID()) + return float64(c), float64(r) +} + +// Nodes returns all the nodes in the grid. +func (l *LimitedVisionGrid) Nodes() []graph.Node { + nodes := make([]graph.Node, 0, len(l.Grid.open)) + for id := range l.Grid.open { + nodes = append(nodes, simple.Node(id)) + } + return nodes +} + +// NodeAt returns the node at (r, c). The returned node may be open or closed. +func (l *LimitedVisionGrid) NodeAt(r, c int) graph.Node { + return l.Grid.NodeAt(r, c) +} + +// Has returns whether n is a node in the grid. +func (l *LimitedVisionGrid) Has(n graph.Node) bool { + return l.has(n.ID()) +} + +func (l *LimitedVisionGrid) has(id int) bool { + return id >= 0 && id < len(l.Grid.open) +} + +// From returns nodes that are optimistically reachable from u. +func (l *LimitedVisionGrid) From(u graph.Node) []graph.Node { + if !l.Has(u) { + return nil + } + + nr, nc := l.RowCol(u.ID()) + var to []graph.Node + for r := nr - 1; r <= nr+1; r++ { + for c := nc - 1; c <= nc+1; c++ { + if v := l.NodeAt(r, c); v != nil && l.HasEdgeBetween(u, v) { + to = append(to, v) + } + } + } + return to +} + +// HasEdgeBetween optimistically returns whether an edge is exists between u and v. +func (l *LimitedVisionGrid) HasEdgeBetween(u, v graph.Node) bool { + if u.ID() == v.ID() { + return false + } + ur, uc := l.RowCol(u.ID()) + vr, vc := l.RowCol(v.ID()) + if abs(ur-vr) > 1 || abs(uc-vc) > 1 { + return false + } + if !l.Grid.AllowDiagonal && ur != vr && uc != vc { + return false + } + + x, y := l.XY(l.Location) + ux, uy := l.XY(u) + vx, vy := l.XY(v) + uKnown := l.Known[u.ID()] || math.Hypot(x-ux, y-uy) <= l.VisionRadius + vKnown := l.Known[v.ID()] || math.Hypot(x-vx, y-vy) <= l.VisionRadius + + switch { + case uKnown && vKnown: + return l.Grid.HasEdgeBetween(u, v) + case uKnown: + return l.Grid.HasOpen(u) + case vKnown: + return l.Grid.HasOpen(v) + default: + return true + } +} + +// Edge optimistically returns the edge from u to v. +func (l *LimitedVisionGrid) Edge(u, v graph.Node) graph.Edge { + return l.EdgeBetween(u, v) +} + +// EdgeBetween optimistically returns the edge between u and v. +func (l *LimitedVisionGrid) EdgeBetween(u, v graph.Node) graph.Edge { + if l.HasEdgeBetween(u, v) { + if !l.Grid.AllowDiagonal || l.Grid.UnitEdgeWeight { + return simple.Edge{F: u, T: v, W: 1} + } + ux, uy := l.XY(u) + vx, vy := l.XY(v) + return simple.Edge{F: u, T: v, W: math.Hypot(ux-vx, uy-vy)} + } + return nil +} + +// Weight returns the weight of the given edge. +func (l *LimitedVisionGrid) Weight(x, y graph.Node) (w float64, ok bool) { + if x.ID() == y.ID() { + return 0, true + } + if !l.HasEdgeBetween(x, y) { + return math.Inf(1), false + } + if e := l.EdgeBetween(x, y); e != nil { + if !l.Grid.AllowDiagonal || l.Grid.UnitEdgeWeight { + return 1, true + } + ux, uy := l.XY(e.From()) + vx, vy := l.XY(e.To()) + return math.Hypot(ux-vx, uy-vy), true + + } + return math.Inf(1), true +} + +// String returns a string representation of the grid. +func (l *LimitedVisionGrid) String() string { + b, _ := l.Render(nil) + return string(b) +} + +// Render returns a text representation of the graph +// with the given path included. If the path is not a path +// in the grid Render returns a non-nil error and the +// path up to that point. +func (l *LimitedVisionGrid) Render(path []graph.Node) ([]byte, error) { + rows, cols := l.Grid.Dims() + b := make([]byte, rows*(cols+1)-1) + for r := 0; r < rows; r++ { + for c := 0; c < cols; c++ { + if !l.Known[r*cols+c] { + b[r*(cols+1)+c] = Unknown + } else if l.Grid.open[r*cols+c] { + b[r*(cols+1)+c] = Open + } else { + b[r*(cols+1)+c] = Closed + } + } + if r < rows-1 { + b[r*(cols+1)+cols] = '\n' + } + } + + // We don't use topo.IsPathIn at the outset because we + // want to draw as much as possible before failing. + for i, n := range path { + if !l.Has(n) || (i != 0 && !l.HasEdgeBetween(path[i-1], n)) { + id := n.ID() + if id >= 0 && id < len(l.Grid.open) { + r, c := l.RowCol(n.ID()) + b[r*(cols+1)+c] = '!' + } + return b, errors.New("grid: not a path in graph") + } + r, c := l.RowCol(n.ID()) + switch i { + case len(path) - 1: + b[r*(cols+1)+c] = 'G' + case 0: + b[r*(cols+1)+c] = 'S' + default: + b[r*(cols+1)+c] = 'o' + } + } + return b, nil +} diff --git a/vendor/github.com/gonum/graph/path/internal/limited_test.go b/vendor/github.com/gonum/graph/path/internal/limited_test.go new file mode 100644 index 000000000000..ef20de41f65f --- /dev/null +++ b/vendor/github.com/gonum/graph/path/internal/limited_test.go @@ -0,0 +1,1242 @@ +// 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 internal + +import ( + "math" + "reflect" + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" +) + +type changes struct { + n graph.Node + + new, old []simple.Edge +} + +var limitedVisionTests = []struct { + g *Grid + radius float64 + diag bool + remember bool + + path []graph.Node + + want []changes +}{ + { + g: NewGridFrom( + "*..*", + "**.*", + "**.*", + "**.*", + ), + radius: 1, + diag: false, + remember: false, + path: []graph.Node{node(1), node(2), node(6), node(10), node(14)}, + + want: []changes{ + { + n: node(1), + new: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(2), + new: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + }, + old: nil, + }, + { + n: node(6), + new: []simple.Edge{ + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + }, + old: nil, + }, + { + n: node(10), + new: []simple.Edge{ + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + }, + old: nil, + }, + { + n: node(14), + new: []simple.Edge{ + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + old: nil, + }, + }, + }, + { + g: NewGridFrom( + "*..*", + "**.*", + "**.*", + "**.*", + ), + radius: 1.5, + diag: false, + remember: false, + path: []graph.Node{node(1), node(2), node(6), node(10), node(14)}, + + want: []changes{ + { + n: node(1), + new: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(0), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(4), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(4), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(2), + new: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(6), + new: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(10), + new: []simple.Edge{ + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(14), + new: []simple.Edge{ + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + old: nil, + }, + }, + }, + { + g: NewGridFrom( + "*..*", + "**.*", + "**.*", + "**.*", + ), + radius: 1, + diag: false, + remember: true, + path: []graph.Node{node(1), node(2), node(6), node(10), node(14)}, + + want: []changes{ + { + n: node(1), + new: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(2), + new: []simple.Edge{ + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + }, + }, + { + n: node(6), + new: []simple.Edge{ + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + }, + old: []simple.Edge{ + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + }, + }, + { + n: node(10), + new: []simple.Edge{ + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + }, + old: []simple.Edge{ + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + }, + }, + { + n: node(14), + new: []simple.Edge{ + {F: simple.Node(13), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + }, + }, + }, + }, + { + g: NewGridFrom( + "*..*", + "**.*", + "**.*", + "**.*", + ), + radius: 1.5, + diag: false, + remember: true, + path: []graph.Node{node(1), node(2), node(6), node(10), node(14)}, + + want: []changes{ + { + n: node(1), + new: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(0), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(4), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(4), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(2), + new: []simple.Edge{ + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + }, + }, + { + n: node(6), + new: []simple.Edge{ + {F: simple.Node(5), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(7), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + }, + }, + { + n: node(10), + new: []simple.Edge{ + {F: simple.Node(9), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(11), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + }, + }, + { + n: node(14), + new: nil, + old: []simple.Edge{ + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + }, + }, + }, + { + g: NewGridFrom( + "*..*", + "**.*", + "**.*", + "**.*", + ), + radius: 1, + diag: true, + remember: false, + path: []graph.Node{node(1), node(2), node(6), node(10), node(14)}, + + want: []changes{ + { + n: node(1), + new: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(0), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(2), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(2), + new: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(6), W: math.Sqrt2}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(1), W: math.Sqrt2}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(3), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(6), + new: []simple.Edge{ + {F: simple.Node(2), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(2), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(7), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(7), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(10), + new: []simple.Edge{ + {F: simple.Node(6), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(6), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(11), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(11), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(14), + new: []simple.Edge{ + {F: simple.Node(10), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(10), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + old: nil, + }, + }, + }, + { + g: NewGridFrom( + "*..*", + "**.*", + "**.*", + "**.*", + ), + radius: 1.5, + diag: true, + remember: false, + path: []graph.Node{node(1), node(2), node(6), node(10), node(14)}, + + want: []changes{ + { + n: node(1), + new: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(0), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(0), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(6), W: math.Sqrt2}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(4), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(4), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(4), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(1), W: math.Sqrt2}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(2), + new: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(6), W: math.Sqrt2}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(2), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(1), W: math.Sqrt2}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(6), + new: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(6), W: math.Sqrt2}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(2), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(1), W: math.Sqrt2}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(6), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(10), + new: []simple.Edge{ + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(6), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(10), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(14), + new: []simple.Edge{ + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(10), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + old: nil, + }, + }, + }, + { + g: NewGridFrom( + "*..*", + "**.*", + "**.*", + "**.*", + ), + radius: 1, + diag: true, + remember: true, + path: []graph.Node{node(1), node(2), node(6), node(10), node(14)}, + + want: []changes{ + { + n: node(1), + new: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(0), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(2), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(2), + new: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(6), W: math.Sqrt2}, + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(1), W: math.Sqrt2}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(5), W: math.Inf(1)}, + }, + }, + { + n: node(6), + new: []simple.Edge{ + {F: simple.Node(2), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(7), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(7), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(5), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(1), W: math.Sqrt2}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + }, + }, + { + n: node(10), + new: []simple.Edge{ + {F: simple.Node(6), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(11), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(11), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(6), T: simple.Node(1), W: math.Sqrt2}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(10), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(7), W: math.Inf(1)}, + }, + }, + { + n: node(14), + new: []simple.Edge{ + {F: simple.Node(10), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(10), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(14), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(11), W: math.Inf(1)}, + }, + }, + }, + }, + { + g: NewGridFrom( + "*..*", + "**.*", + "**.*", + "**.*", + ), + radius: 1.5, + diag: true, + remember: true, + path: []graph.Node{node(1), node(2), node(6), node(10), node(14)}, + + want: []changes{ + { + n: node(1), + new: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(0), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(0), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(6), W: math.Sqrt2}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(4), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(4), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(4), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(1), W: math.Sqrt2}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + }, + old: nil, + }, + { + n: node(2), + new: []simple.Edge{ + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(6), W: math.Sqrt2}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(5), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(1), W: math.Sqrt2}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + }, + }, + { + n: node(6), + new: []simple.Edge{ + {F: simple.Node(5), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(6), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(1), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(1), T: simple.Node(6), W: math.Sqrt2}, + {F: simple.Node(2), T: simple.Node(1), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(2), T: simple.Node(6), W: 1}, + {F: simple.Node(2), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(3), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(1), W: math.Sqrt2}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + }, + }, + { + n: node(10), + new: []simple.Edge{ + {F: simple.Node(9), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(10), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + old: []simple.Edge{ + {F: simple.Node(5), T: simple.Node(0), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(1), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(5), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(1), W: math.Sqrt2}, + {F: simple.Node(6), T: simple.Node(2), W: 1}, + {F: simple.Node(6), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(6), T: simple.Node(10), W: 1}, + {F: simple.Node(6), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(2), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(3), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(7), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + }, + }, + { + n: node(14), + new: nil, + old: []simple.Edge{ + {F: simple.Node(9), T: simple.Node(4), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(9), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(5), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(6), W: 1}, + {F: simple.Node(10), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(10), T: simple.Node(14), W: 1}, + {F: simple.Node(10), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(6), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(7), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(11), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(13), T: simple.Node(14), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(9), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(10), W: 1}, + {F: simple.Node(14), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(13), W: math.Inf(1)}, + {F: simple.Node(14), T: simple.Node(15), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(10), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(11), W: math.Inf(1)}, + {F: simple.Node(15), T: simple.Node(14), W: math.Inf(1)}, + }, + }, + }, + }, +} + +func TestLimitedVisionGrid(t *testing.T) { + for i, test := range limitedVisionTests { + l := &LimitedVisionGrid{ + Grid: test.g, + VisionRadius: test.radius, + Location: test.path[0], + } + if test.remember { + l.Known = make(map[int]bool) + } + l.Grid.AllowDiagonal = test.diag + + x, y := l.XY(test.path[0]) + for _, u := range l.Nodes() { + ux, uy := l.XY(u) + uNear := math.Hypot(x-ux, y-uy) <= test.radius + for _, v := range l.Nodes() { + vx, vy := l.XY(v) + vNear := math.Hypot(x-vx, y-vy) <= test.radius + if u.ID() == v.ID() && l.HasEdgeBetween(u, v) { + t.Errorf("unexpected self edge: %v -- %v", u, v) + } + if !uNear && !vNear && !l.HasEdgeBetween(u, v) && couldConnectIn(l, u, v) { + t.Errorf("unexpected pessimism: no hope in distant edge between %v and %v for test %d", + u, v, i) + } + if (uNear && vNear) && l.HasEdgeBetween(u, v) != l.Grid.HasEdgeBetween(u, v) { + t.Errorf("unrealistic optimism: disagreement about edge between %v and %v for test %d: got:%t want:%t", + u, v, i, l.HasEdgeBetween(u, v), l.Grid.HasEdgeBetween(u, v)) + } + } + } + + var got []changes + for _, n := range test.path { + new, old := l.MoveTo(n) + got = append(got, changes{n: n, new: asConcreteEdges(new, l), old: asConcreteEdges(old, l)}) + } + if !reflect.DeepEqual(got, test.want) { + t.Errorf("unexpected walk for test %d:\ngot: %+v\nwant:%+v", i, got, test.want) + } + } +} + +func asConcreteEdges(changes []graph.Edge, in graph.Weighter) []simple.Edge { + if changes == nil { + return nil + } + we := make([]simple.Edge, len(changes)) + for i, e := range changes { + we[i].F = e.From() + we[i].T = e.To() + w, ok := in.Weight(e.From(), e.To()) + if !ok && !math.IsInf(w, 1) { + panic("unexpected invalid finite weight") + } + we[i].W = w + } + return we +} + +func couldConnectIn(l *LimitedVisionGrid, u, v graph.Node) bool { + if u.ID() == v.ID() { + return false + } + + ur, uc := l.RowCol(u.ID()) + vr, vc := l.RowCol(v.ID()) + if abs(ur-vr) > 1 || abs(uc-vc) > 1 { + return false + } + if (ur != vr || uc != vc) && !l.Grid.AllowDiagonal { + return false + } + + if !l.Known[u.ID()] && !l.Known[v.ID()] { + return true + } + if l.Known[u.ID()] && !l.Grid.HasOpen(u) { + return false + } + if l.Known[v.ID()] && !l.Grid.HasOpen(v) { + return false + } + + return true +} diff --git a/vendor/github.com/gonum/graph/path/internal/testgraphs/shortest.go b/vendor/github.com/gonum/graph/path/internal/testgraphs/shortest.go new file mode 100644 index 000000000000..f512e382ed7c --- /dev/null +++ b/vendor/github.com/gonum/graph/path/internal/testgraphs/shortest.go @@ -0,0 +1,654 @@ +// Copyright ©2014 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 testgraphs + +import ( + "fmt" + "math" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" +) + +func init() { + for _, test := range ShortestPathTests { + if len(test.WantPaths) != 1 && test.HasUniquePath { + panic(fmt.Sprintf("%q: bad shortest path test: non-unique paths marked unique", test.Name)) + } + } +} + +// ShortestPathTests are graphs used to test the static shortest path routines in path: BellmanFord, +// DijkstraAllPaths, DijkstraFrom, FloydWarshall and Johnson, and the static degenerate case for the +// dynamic shortest path routine in path/dynamic: DStarLite. +var ShortestPathTests = []struct { + Name string + Graph func() graph.EdgeSetter + Edges []simple.Edge + HasNegativeWeight bool + HasNegativeCycle bool + + Query simple.Edge + Weight float64 + WantPaths [][]int + HasUniquePath bool + + NoPathFor simple.Edge +}{ + // Positive weighted graphs. + { + Name: "empty directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, + Weight: math.Inf(1), + + NoPathFor: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, + }, + { + Name: "empty undirected", + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, + Weight: math.Inf(1), + + NoPathFor: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, + }, + { + Name: "one edge directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: 1}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, + Weight: 1, + WantPaths: [][]int{ + {0, 1}, + }, + HasUniquePath: true, + + NoPathFor: simple.Edge{F: simple.Node(2), T: simple.Node(3)}, + }, + { + Name: "one edge self directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: 1}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(0)}, + Weight: 0, + WantPaths: [][]int{ + {0}, + }, + HasUniquePath: true, + + NoPathFor: simple.Edge{F: simple.Node(2), T: simple.Node(3)}, + }, + { + Name: "one edge undirected", + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: 1}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, + Weight: 1, + WantPaths: [][]int{ + {0, 1}, + }, + HasUniquePath: true, + + NoPathFor: simple.Edge{F: simple.Node(2), T: simple.Node(3)}, + }, + { + Name: "two paths directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(2), W: 2}, + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(2)}, + Weight: 2, + WantPaths: [][]int{ + {0, 1, 2}, + {0, 2}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(2), T: simple.Node(1)}, + }, + { + Name: "two paths undirected", + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(2), W: 2}, + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(2)}, + Weight: 2, + WantPaths: [][]int{ + {0, 1, 2}, + {0, 2}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(2), T: simple.Node(4)}, + }, + { + Name: "confounding paths directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + // Add a path from 0->5 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(5), W: 1}, + + // Add direct edge to goal of weight 4 + {F: simple.Node(0), T: simple.Node(5), W: 4}, + + // Add edge to a node that's still optimal + {F: simple.Node(0), T: simple.Node(2), W: 2}, + + // Add edge to 3 that's overpriced + {F: simple.Node(0), T: simple.Node(3), W: 4}, + + // Add very cheap edge to 4 which is a dead end + {F: simple.Node(0), T: simple.Node(4), W: 0.25}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(5)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 5}, + {0, 2, 3, 5}, + {0, 5}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(4), T: simple.Node(5)}, + }, + { + Name: "confounding paths undirected", + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + // Add a path from 0->5 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(5), W: 1}, + + // Add direct edge to goal of weight 4 + {F: simple.Node(0), T: simple.Node(5), W: 4}, + + // Add edge to a node that's still optimal + {F: simple.Node(0), T: simple.Node(2), W: 2}, + + // Add edge to 3 that's overpriced + {F: simple.Node(0), T: simple.Node(3), W: 4}, + + // Add very cheap edge to 4 which is a dead end + {F: simple.Node(0), T: simple.Node(4), W: 0.25}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(5)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 5}, + {0, 2, 3, 5}, + {0, 5}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(5), T: simple.Node(6)}, + }, + { + Name: "confounding paths directed 2-step", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + // Add a path from 0->5 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(5), W: 1}, + + // Add two step path to goal of weight 4 + {F: simple.Node(0), T: simple.Node(6), W: 2}, + {F: simple.Node(6), T: simple.Node(5), W: 2}, + + // Add edge to a node that's still optimal + {F: simple.Node(0), T: simple.Node(2), W: 2}, + + // Add edge to 3 that's overpriced + {F: simple.Node(0), T: simple.Node(3), W: 4}, + + // Add very cheap edge to 4 which is a dead end + {F: simple.Node(0), T: simple.Node(4), W: 0.25}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(5)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 5}, + {0, 2, 3, 5}, + {0, 6, 5}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(4), T: simple.Node(5)}, + }, + { + Name: "confounding paths undirected 2-step", + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + // Add a path from 0->5 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(5), W: 1}, + + // Add two step path to goal of weight 4 + {F: simple.Node(0), T: simple.Node(6), W: 2}, + {F: simple.Node(6), T: simple.Node(5), W: 2}, + + // Add edge to a node that's still optimal + {F: simple.Node(0), T: simple.Node(2), W: 2}, + + // Add edge to 3 that's overpriced + {F: simple.Node(0), T: simple.Node(3), W: 4}, + + // Add very cheap edge to 4 which is a dead end + {F: simple.Node(0), T: simple.Node(4), W: 0.25}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(5)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 5}, + {0, 2, 3, 5}, + {0, 6, 5}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(5), T: simple.Node(7)}, + }, + { + Name: "zero-weight cycle directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + // Add a path from 0->4 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(4), W: 1}, + + // Add a zero-weight cycle. + {F: simple.Node(1), T: simple.Node(5), W: 0}, + {F: simple.Node(5), T: simple.Node(1), W: 0}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(4)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 4}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(4), T: simple.Node(5)}, + }, + { + Name: "zero-weight cycle^2 directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + // Add a path from 0->4 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(4), W: 1}, + + // Add a zero-weight cycle. + {F: simple.Node(1), T: simple.Node(5), W: 0}, + {F: simple.Node(5), T: simple.Node(1), W: 0}, + // With its own zero-weight cycle. + {F: simple.Node(5), T: simple.Node(6), W: 0}, + {F: simple.Node(6), T: simple.Node(5), W: 0}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(4)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 4}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(4), T: simple.Node(5)}, + }, + { + Name: "zero-weight cycle^2 confounding directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + // Add a path from 0->4 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(4), W: 1}, + + // Add a zero-weight cycle. + {F: simple.Node(1), T: simple.Node(5), W: 0}, + {F: simple.Node(5), T: simple.Node(1), W: 0}, + // With its own zero-weight cycle. + {F: simple.Node(5), T: simple.Node(6), W: 0}, + {F: simple.Node(6), T: simple.Node(5), W: 0}, + // But leading to the target. + {F: simple.Node(5), T: simple.Node(4), W: 3}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(4)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 4}, + {0, 1, 5, 4}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(4), T: simple.Node(5)}, + }, + { + Name: "zero-weight cycle^3 directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + // Add a path from 0->4 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(4), W: 1}, + + // Add a zero-weight cycle. + {F: simple.Node(1), T: simple.Node(5), W: 0}, + {F: simple.Node(5), T: simple.Node(1), W: 0}, + // With its own zero-weight cycle. + {F: simple.Node(5), T: simple.Node(6), W: 0}, + {F: simple.Node(6), T: simple.Node(5), W: 0}, + // With its own zero-weight cycle. + {F: simple.Node(6), T: simple.Node(7), W: 0}, + {F: simple.Node(7), T: simple.Node(6), W: 0}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(4)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 4}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(4), T: simple.Node(5)}, + }, + { + Name: "zero-weight 3·cycle^2 confounding directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + // Add a path from 0->4 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(4), W: 1}, + + // Add a zero-weight cycle. + {F: simple.Node(1), T: simple.Node(5), W: 0}, + {F: simple.Node(5), T: simple.Node(1), W: 0}, + // With 3 of its own zero-weight cycles. + {F: simple.Node(5), T: simple.Node(6), W: 0}, + {F: simple.Node(6), T: simple.Node(5), W: 0}, + {F: simple.Node(5), T: simple.Node(7), W: 0}, + {F: simple.Node(7), T: simple.Node(5), W: 0}, + // Each leading to the target. + {F: simple.Node(5), T: simple.Node(4), W: 3}, + {F: simple.Node(6), T: simple.Node(4), W: 3}, + {F: simple.Node(7), T: simple.Node(4), W: 3}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(4)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 4}, + {0, 1, 5, 4}, + {0, 1, 5, 6, 4}, + {0, 1, 5, 7, 4}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(4), T: simple.Node(5)}, + }, + { + Name: "zero-weight reversed 3·cycle^2 confounding directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + // Add a path from 0->4 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(4), W: 1}, + + // Add a zero-weight cycle. + {F: simple.Node(3), T: simple.Node(5), W: 0}, + {F: simple.Node(5), T: simple.Node(3), W: 0}, + // With 3 of its own zero-weight cycles. + {F: simple.Node(5), T: simple.Node(6), W: 0}, + {F: simple.Node(6), T: simple.Node(5), W: 0}, + {F: simple.Node(5), T: simple.Node(7), W: 0}, + {F: simple.Node(7), T: simple.Node(5), W: 0}, + // Each leading from the source. + {F: simple.Node(0), T: simple.Node(5), W: 3}, + {F: simple.Node(0), T: simple.Node(6), W: 3}, + {F: simple.Node(0), T: simple.Node(7), W: 3}, + }, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(4)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 4}, + {0, 5, 3, 4}, + {0, 6, 5, 3, 4}, + {0, 7, 5, 3, 4}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(4), T: simple.Node(5)}, + }, + { + Name: "zero-weight |V|·cycle^(n/|V|) directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: func() []simple.Edge { + e := []simple.Edge{ + // Add a path from 0->4 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(4), W: 1}, + } + next := len(e) + 1 + + // Add n zero-weight cycles. + const n = 100 + for i := 0; i < n; i++ { + e = append(e, + simple.Edge{F: simple.Node(next + i), T: simple.Node(i), W: 0}, + simple.Edge{F: simple.Node(i), T: simple.Node(next + i), W: 0}, + ) + } + return e + }(), + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(4)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 4}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(4), T: simple.Node(5)}, + }, + { + Name: "zero-weight n·cycle directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: func() []simple.Edge { + e := []simple.Edge{ + // Add a path from 0->4 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(4), W: 1}, + } + next := len(e) + 1 + + // Add n zero-weight cycles. + const n = 100 + for i := 0; i < n; i++ { + e = append(e, + simple.Edge{F: simple.Node(next + i), T: simple.Node(1), W: 0}, + simple.Edge{F: simple.Node(1), T: simple.Node(next + i), W: 0}, + ) + } + return e + }(), + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(4)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 4}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(4), T: simple.Node(5)}, + }, + { + Name: "zero-weight bi-directional tree with single exit directed", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: func() []simple.Edge { + e := []simple.Edge{ + // Add a path from 0->4 of weight 4 + {F: simple.Node(0), T: simple.Node(1), W: 1}, + {F: simple.Node(1), T: simple.Node(2), W: 1}, + {F: simple.Node(2), T: simple.Node(3), W: 1}, + {F: simple.Node(3), T: simple.Node(4), W: 1}, + } + + // Make a bi-directional tree rooted at node 2 with + // a single exit to node 4 and co-equal cost from + // 2 to 4. + const ( + depth = 4 + branching = 4 + ) + + next := len(e) + 1 + src := 2 + var i, last int + for l := 0; l < depth; l++ { + for i = 0; i < branching; i++ { + last = next + i + e = append(e, simple.Edge{F: simple.Node(src), T: simple.Node(last), W: 0}) + e = append(e, simple.Edge{F: simple.Node(last), T: simple.Node(src), W: 0}) + } + src = next + 1 + next += branching + } + e = append(e, simple.Edge{F: simple.Node(last), T: simple.Node(4), W: 2}) + return e + }(), + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(4)}, + Weight: 4, + WantPaths: [][]int{ + {0, 1, 2, 3, 4}, + {0, 1, 2, 6, 10, 14, 20, 4}, + }, + HasUniquePath: false, + + NoPathFor: simple.Edge{F: simple.Node(4), T: simple.Node(5)}, + }, + + // Negative weighted graphs. + { + Name: "one edge directed negative", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: -1}, + }, + HasNegativeWeight: true, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, + Weight: -1, + WantPaths: [][]int{ + {0, 1}, + }, + HasUniquePath: true, + + NoPathFor: simple.Edge{F: simple.Node(2), T: simple.Node(3)}, + }, + { + Name: "one edge undirected negative", + Graph: func() graph.EdgeSetter { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + {F: simple.Node(0), T: simple.Node(1), W: -1}, + }, + HasNegativeWeight: true, + HasNegativeCycle: true, + + Query: simple.Edge{F: simple.Node(0), T: simple.Node(1)}, + }, + { + Name: "wp graph negative", // http://en.wikipedia.org/w/index.php?title=Johnson%27s_algorithm&oldid=564595231 + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + {F: simple.Node('w'), T: simple.Node('z'), W: 2}, + {F: simple.Node('x'), T: simple.Node('w'), W: 6}, + {F: simple.Node('x'), T: simple.Node('y'), W: 3}, + {F: simple.Node('y'), T: simple.Node('w'), W: 4}, + {F: simple.Node('y'), T: simple.Node('z'), W: 5}, + {F: simple.Node('z'), T: simple.Node('x'), W: -7}, + {F: simple.Node('z'), T: simple.Node('y'), W: -3}, + }, + HasNegativeWeight: true, + + Query: simple.Edge{F: simple.Node('z'), T: simple.Node('y')}, + Weight: -4, + WantPaths: [][]int{ + {'z', 'x', 'y'}, + }, + HasUniquePath: true, + + NoPathFor: simple.Edge{F: simple.Node(2), T: simple.Node(3)}, + }, + { + Name: "roughgarden negative", + Graph: func() graph.EdgeSetter { return simple.NewDirectedGraph(0, math.Inf(1)) }, + Edges: []simple.Edge{ + {F: simple.Node('a'), T: simple.Node('b'), W: -2}, + {F: simple.Node('b'), T: simple.Node('c'), W: -1}, + {F: simple.Node('c'), T: simple.Node('a'), W: 4}, + {F: simple.Node('c'), T: simple.Node('x'), W: 2}, + {F: simple.Node('c'), T: simple.Node('y'), W: -3}, + {F: simple.Node('z'), T: simple.Node('x'), W: 1}, + {F: simple.Node('z'), T: simple.Node('y'), W: -4}, + }, + HasNegativeWeight: true, + + Query: simple.Edge{F: simple.Node('a'), T: simple.Node('y')}, + Weight: -6, + WantPaths: [][]int{ + {'a', 'b', 'c', 'y'}, + }, + HasUniquePath: true, + + NoPathFor: simple.Edge{F: simple.Node(2), T: simple.Node(3)}, + }, +} diff --git a/vendor/github.com/gonum/graph/path/johnson_apsp.go b/vendor/github.com/gonum/graph/path/johnson_apsp.go index 1ea195e0badd..c69dfb079686 100644 --- a/vendor/github.com/gonum/graph/path/johnson_apsp.go +++ b/vendor/github.com/gonum/graph/path/johnson_apsp.go @@ -9,11 +9,11 @@ import ( "math/rand" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" + "github.com/gonum/graph/simple" ) // JohnsonAllPaths returns a shortest-path tree for shortest paths in the graph g. -// If the graph does not implement graph.Weighter, graph.UniformCost is used. +// If the graph does not implement graph.Weighter, UniformCost is used. // // The time complexity of JohnsonAllPaths is O(|V|.|E|+|V|^2.log|V|). func JohnsonAllPaths(g graph.Graph) (paths AllShortest, ok bool) { @@ -22,10 +22,10 @@ func JohnsonAllPaths(g graph.Graph) (paths AllShortest, ok bool) { from: g.From, edgeTo: g.Edge, } - if g, ok := g.(graph.Weighter); ok { - jg.weight = g.Weight + if wg, ok := g.(graph.Weighter); ok { + jg.weight = wg.Weight } else { - jg.weight = graph.UniformCost + jg.weight = UniformCost(g) } paths = newAllShortest(g.Nodes(), false) @@ -70,7 +70,7 @@ type johnsonWeightAdjuster struct { from func(graph.Node) []graph.Node edgeTo func(graph.Node, graph.Node) graph.Edge - weight graph.WeightFunc + weight Weighting bellmanFord bool adjustBy Shortest @@ -109,27 +109,28 @@ func (g johnsonWeightAdjuster) From(n graph.Node) []graph.Node { func (g johnsonWeightAdjuster) Edge(u, v graph.Node) graph.Edge { if g.bellmanFord && u.ID() == g.q && g.g.Has(v) { - return concrete.Edge{johnsonGraphNode(g.q), v} + return simple.Edge{F: johnsonGraphNode(g.q), T: v} } return g.edgeTo(u, v) } -func (g johnsonWeightAdjuster) Weight(e graph.Edge) float64 { +func (g johnsonWeightAdjuster) Weight(x, y graph.Node) (w float64, ok bool) { if g.bellmanFord { switch g.q { - case e.From().ID(): - return 0 - case e.To().ID(): - return math.Inf(1) + case x.ID(): + return 0, true + case y.ID(): + return math.Inf(1), false default: - return g.weight(e) + return g.weight(x, y) } } - return g.weight(e) + g.adjustBy.WeightTo(e.From()) - g.adjustBy.WeightTo(e.To()) + w, ok = g.weight(x, y) + return w + g.adjustBy.WeightTo(x) - g.adjustBy.WeightTo(y), ok } -func (johnsonWeightAdjuster) HasEdge(_, _ graph.Node) bool { - panic("search: unintended use of johnsonWeightAdjuster") +func (johnsonWeightAdjuster) HasEdgeBetween(_, _ graph.Node) bool { + panic("path: unintended use of johnsonWeightAdjuster") } type johnsonGraphNode int diff --git a/vendor/github.com/gonum/graph/path/johnson_apsp_test.go b/vendor/github.com/gonum/graph/path/johnson_apsp_test.go index 814000f08ec3..8fe05d13cf4f 100644 --- a/vendor/github.com/gonum/graph/path/johnson_apsp_test.go +++ b/vendor/github.com/gonum/graph/path/johnson_apsp_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package path_test +package path import ( "math" @@ -11,50 +11,50 @@ import ( "testing" "github.com/gonum/graph" - "github.com/gonum/graph/internal" - "github.com/gonum/graph/path" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/graph/path/internal/testgraphs" ) func TestJohnsonAllPaths(t *testing.T) { - for _, test := range shortestPathTests { - g := test.g() - for _, e := range test.edges { - g.SetEdge(e, e.Cost) + for _, test := range testgraphs.ShortestPathTests { + g := test.Graph() + for _, e := range test.Edges { + g.SetEdge(e) } - pt, ok := path.JohnsonAllPaths(g.(graph.Graph)) - if test.hasNegativeCycle { + pt, ok := JohnsonAllPaths(g.(graph.Graph)) + if test.HasNegativeCycle { if ok { - t.Errorf("%q: expected negative cycle", test.name) + t.Errorf("%q: expected negative cycle", test.Name) } continue } if !ok { - t.Fatalf("%q: unexpected negative cycle", test.name) + t.Fatalf("%q: unexpected negative cycle", test.Name) } // Check all random paths returned are OK. for i := 0; i < 10; i++ { - p, weight, unique := pt.Between(test.query.From(), test.query.To()) - if weight != test.weight { + p, weight, unique := pt.Between(test.Query.From(), test.Query.To()) + if weight != test.Weight { t.Errorf("%q: unexpected weight from Between: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } - if weight := pt.Weight(test.query.From(), test.query.To()); weight != test.weight { + if weight := pt.Weight(test.Query.From(), test.Query.To()); weight != test.Weight { t.Errorf("%q: unexpected weight from Weight: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } - if unique != test.unique { + if unique != test.HasUniquePath { t.Errorf("%q: unexpected number of paths: got: unique=%t want: unique=%t", - test.name, unique, test.unique) + test.Name, unique, test.HasUniquePath) } var got []int for _, n := range p { got = append(got, n.ID()) } - ok := len(got) == 0 && len(test.want) == 0 - for _, sp := range test.want { + ok := len(got) == 0 && len(test.WantPaths) == 0 + for _, sp := range test.WantPaths { if reflect.DeepEqual(got, sp) { ok = true break @@ -62,20 +62,20 @@ func TestJohnsonAllPaths(t *testing.T) { } if !ok { t.Errorf("%q: unexpected shortest path:\ngot: %v\nwant from:%v", - test.name, p, test.want) + test.Name, p, test.WantPaths) } } - np, weight, unique := pt.Between(test.none.From(), test.none.To()) + np, weight, unique := pt.Between(test.NoPathFor.From(), test.NoPathFor.To()) if np != nil || !math.IsInf(weight, 1) || unique != false { t.Errorf("%q: unexpected path:\ngot: path=%v weight=%f unique=%t\nwant:path= weight=+Inf unique=false", - test.name, np, weight, unique) + test.Name, np, weight, unique) } - paths, weight := pt.AllBetween(test.query.From(), test.query.To()) - if weight != test.weight { + paths, weight := pt.AllBetween(test.Query.From(), test.Query.To()) + if weight != test.Weight { t.Errorf("%q: unexpected weight from Between: got:%f want:%f", - test.name, weight, test.weight) + test.Name, weight, test.Weight) } var got [][]int @@ -87,16 +87,16 @@ func TestJohnsonAllPaths(t *testing.T) { got[i] = append(got[i], v.ID()) } } - sort.Sort(internal.BySliceValues(got)) - if !reflect.DeepEqual(got, test.want) { + sort.Sort(ordered.BySliceValues(got)) + if !reflect.DeepEqual(got, test.WantPaths) { t.Errorf("testing %q: unexpected shortest paths:\ngot: %v\nwant:%v", - test.name, got, test.want) + test.Name, got, test.WantPaths) } - nps, weight := pt.AllBetween(test.none.From(), test.none.To()) + nps, weight := pt.AllBetween(test.NoPathFor.From(), test.NoPathFor.To()) if nps != nil || !math.IsInf(weight, 1) { t.Errorf("%q: unexpected path:\ngot: paths=%v weight=%f\nwant:path= weight=+Inf", - test.name, nps, weight) + test.Name, nps, weight) } } } diff --git a/vendor/github.com/gonum/graph/path/shortest_test.go b/vendor/github.com/gonum/graph/path/shortest_test.go deleted file mode 100644 index b19e1463c44a..000000000000 --- a/vendor/github.com/gonum/graph/path/shortest_test.go +++ /dev/null @@ -1,653 +0,0 @@ -// Copyright ©2014 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 path_test - -import ( - "fmt" - "math" - - "github.com/gonum/graph" - "github.com/gonum/graph/concrete" -) - -func init() { - for _, test := range shortestPathTests { - if len(test.want) != 1 && test.unique { - panic(fmt.Sprintf("%q: bad shortest path test: non-unique paths marked unique", test.name)) - } - } -} - -// shortestPathTests are graphs used to test BellmanFord, -// DijkstraAllPaths, DijkstraFrom, FloydWarshall and Johnson. -var shortestPathTests = []struct { - name string - g func() graph.Mutable - edges []concrete.WeightedEdge - negative bool - hasNegativeCycle bool - - query concrete.Edge - weight float64 - want [][]int - unique bool - - none concrete.Edge -}{ - // Positive weighted graphs. - { - name: "empty directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(1)}, - weight: math.Inf(1), - - none: concrete.Edge{concrete.Node(0), concrete.Node(1)}, - }, - { - name: "empty undirected", - g: func() graph.Mutable { return concrete.NewGraph() }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(1)}, - weight: math.Inf(1), - - none: concrete.Edge{concrete.Node(0), concrete.Node(1)}, - }, - { - name: "one edge directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(1)}, - weight: 1, - want: [][]int{ - {0, 1}, - }, - unique: true, - - none: concrete.Edge{concrete.Node(2), concrete.Node(3)}, - }, - { - name: "one edge self directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(0)}, - weight: 0, - want: [][]int{ - {0}, - }, - unique: true, - - none: concrete.Edge{concrete.Node(2), concrete.Node(3)}, - }, - { - name: "one edge undirected", - g: func() graph.Mutable { return concrete.NewGraph() }, - edges: []concrete.WeightedEdge{ - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(1)}, - weight: 1, - want: [][]int{ - {0, 1}, - }, - unique: true, - - none: concrete.Edge{concrete.Node(2), concrete.Node(3)}, - }, - { - name: "two paths directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - {concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2}, - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(2)}, - weight: 2, - want: [][]int{ - {0, 1, 2}, - {0, 2}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(2), concrete.Node(1)}, - }, - { - name: "two paths undirected", - g: func() graph.Mutable { return concrete.NewGraph() }, - edges: []concrete.WeightedEdge{ - {concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2}, - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(2)}, - weight: 2, - want: [][]int{ - {0, 1, 2}, - {0, 2}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(2), concrete.Node(4)}, - }, - { - name: "confounding paths directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - // Add a path from 0->5 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(5)}, 1}, - - // Add direct edge to goal of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(5)}, 4}, - - // Add edge to a node that's still optimal - {concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2}, - - // Add edge to 3 that's overpriced - {concrete.Edge{concrete.Node(0), concrete.Node(3)}, 4}, - - // Add very cheap edge to 4 which is a dead end - {concrete.Edge{concrete.Node(0), concrete.Node(4)}, 0.25}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(5)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 5}, - {0, 2, 3, 5}, - {0, 5}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, - }, - { - name: "confounding paths undirected", - g: func() graph.Mutable { return concrete.NewGraph() }, - edges: []concrete.WeightedEdge{ - // Add a path from 0->5 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(5)}, 1}, - - // Add direct edge to goal of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(5)}, 4}, - - // Add edge to a node that's still optimal - {concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2}, - - // Add edge to 3 that's overpriced - {concrete.Edge{concrete.Node(0), concrete.Node(3)}, 4}, - - // Add very cheap edge to 4 which is a dead end - {concrete.Edge{concrete.Node(0), concrete.Node(4)}, 0.25}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(5)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 5}, - {0, 2, 3, 5}, - {0, 5}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(5), concrete.Node(6)}, - }, - { - name: "confounding paths directed 2-step", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - // Add a path from 0->5 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(5)}, 1}, - - // Add two step path to goal of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(6)}, 2}, - {concrete.Edge{concrete.Node(6), concrete.Node(5)}, 2}, - - // Add edge to a node that's still optimal - {concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2}, - - // Add edge to 3 that's overpriced - {concrete.Edge{concrete.Node(0), concrete.Node(3)}, 4}, - - // Add very cheap edge to 4 which is a dead end - {concrete.Edge{concrete.Node(0), concrete.Node(4)}, 0.25}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(5)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 5}, - {0, 2, 3, 5}, - {0, 6, 5}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, - }, - { - name: "confounding paths undirected 2-step", - g: func() graph.Mutable { return concrete.NewGraph() }, - edges: []concrete.WeightedEdge{ - // Add a path from 0->5 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(5)}, 1}, - - // Add two step path to goal of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(6)}, 2}, - {concrete.Edge{concrete.Node(6), concrete.Node(5)}, 2}, - - // Add edge to a node that's still optimal - {concrete.Edge{concrete.Node(0), concrete.Node(2)}, 2}, - - // Add edge to 3 that's overpriced - {concrete.Edge{concrete.Node(0), concrete.Node(3)}, 4}, - - // Add very cheap edge to 4 which is a dead end - {concrete.Edge{concrete.Node(0), concrete.Node(4)}, 0.25}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(5)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 5}, - {0, 2, 3, 5}, - {0, 6, 5}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(5), concrete.Node(7)}, - }, - { - name: "zero-weight cycle directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - // Add a path from 0->4 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, - - // Add a zero-weight cycle. - {concrete.Edge{concrete.Node(1), concrete.Node(5)}, 0}, - {concrete.Edge{concrete.Node(5), concrete.Node(1)}, 0}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 4}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, - }, - { - name: "zero-weight cycle^2 directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - // Add a path from 0->4 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, - - // Add a zero-weight cycle. - {concrete.Edge{concrete.Node(1), concrete.Node(5)}, 0}, - {concrete.Edge{concrete.Node(5), concrete.Node(1)}, 0}, - // With its own zero-weight cycle. - {concrete.Edge{concrete.Node(5), concrete.Node(6)}, 0}, - {concrete.Edge{concrete.Node(6), concrete.Node(5)}, 0}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 4}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, - }, - { - name: "zero-weight cycle^2 confounding directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - // Add a path from 0->4 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, - - // Add a zero-weight cycle. - {concrete.Edge{concrete.Node(1), concrete.Node(5)}, 0}, - {concrete.Edge{concrete.Node(5), concrete.Node(1)}, 0}, - // With its own zero-weight cycle. - {concrete.Edge{concrete.Node(5), concrete.Node(6)}, 0}, - {concrete.Edge{concrete.Node(6), concrete.Node(5)}, 0}, - // But leading to the target. - {concrete.Edge{concrete.Node(5), concrete.Node(4)}, 3}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 4}, - {0, 1, 5, 4}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, - }, - { - name: "zero-weight cycle^3 directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - // Add a path from 0->4 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, - - // Add a zero-weight cycle. - {concrete.Edge{concrete.Node(1), concrete.Node(5)}, 0}, - {concrete.Edge{concrete.Node(5), concrete.Node(1)}, 0}, - // With its own zero-weight cycle. - {concrete.Edge{concrete.Node(5), concrete.Node(6)}, 0}, - {concrete.Edge{concrete.Node(6), concrete.Node(5)}, 0}, - // With its own zero-weight cycle. - {concrete.Edge{concrete.Node(6), concrete.Node(7)}, 0}, - {concrete.Edge{concrete.Node(7), concrete.Node(6)}, 0}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 4}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, - }, - { - name: "zero-weight 3·cycle^2 confounding directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - // Add a path from 0->4 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, - - // Add a zero-weight cycle. - {concrete.Edge{concrete.Node(1), concrete.Node(5)}, 0}, - {concrete.Edge{concrete.Node(5), concrete.Node(1)}, 0}, - // With 3 of its own zero-weight cycles. - {concrete.Edge{concrete.Node(5), concrete.Node(6)}, 0}, - {concrete.Edge{concrete.Node(6), concrete.Node(5)}, 0}, - {concrete.Edge{concrete.Node(5), concrete.Node(7)}, 0}, - {concrete.Edge{concrete.Node(7), concrete.Node(5)}, 0}, - // Each leading to the target. - {concrete.Edge{concrete.Node(5), concrete.Node(4)}, 3}, - {concrete.Edge{concrete.Node(6), concrete.Node(4)}, 3}, - {concrete.Edge{concrete.Node(7), concrete.Node(4)}, 3}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 4}, - {0, 1, 5, 4}, - {0, 1, 5, 6, 4}, - {0, 1, 5, 7, 4}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, - }, - { - name: "zero-weight reversed 3·cycle^2 confounding directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - // Add a path from 0->4 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, - - // Add a zero-weight cycle. - {concrete.Edge{concrete.Node(3), concrete.Node(5)}, 0}, - {concrete.Edge{concrete.Node(5), concrete.Node(3)}, 0}, - // With 3 of its own zero-weight cycles. - {concrete.Edge{concrete.Node(5), concrete.Node(6)}, 0}, - {concrete.Edge{concrete.Node(6), concrete.Node(5)}, 0}, - {concrete.Edge{concrete.Node(5), concrete.Node(7)}, 0}, - {concrete.Edge{concrete.Node(7), concrete.Node(5)}, 0}, - // Each leading from the source. - {concrete.Edge{concrete.Node(0), concrete.Node(5)}, 3}, - {concrete.Edge{concrete.Node(0), concrete.Node(6)}, 3}, - {concrete.Edge{concrete.Node(0), concrete.Node(7)}, 3}, - }, - - query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 4}, - {0, 5, 3, 4}, - {0, 6, 5, 3, 4}, - {0, 7, 5, 3, 4}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, - }, - { - name: "zero-weight |V|·cycle^(n/|V|) directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: func() []concrete.WeightedEdge { - e := []concrete.WeightedEdge{ - // Add a path from 0->4 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, - } - next := len(e) + 1 - - // Add n zero-weight cycles. - const n = 100 - for i := 0; i < n; i++ { - e = append(e, - concrete.WeightedEdge{concrete.Edge{concrete.Node(next + i), concrete.Node(i)}, 0}, - concrete.WeightedEdge{concrete.Edge{concrete.Node(i), concrete.Node(next + i)}, 0}, - ) - } - return e - }(), - - query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 4}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, - }, - { - name: "zero-weight n·cycle directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: func() []concrete.WeightedEdge { - e := []concrete.WeightedEdge{ - // Add a path from 0->4 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, - } - next := len(e) + 1 - - // Add n zero-weight cycles. - const n = 100 - for i := 0; i < n; i++ { - e = append(e, - concrete.WeightedEdge{concrete.Edge{concrete.Node(next + i), concrete.Node(1)}, 0}, - concrete.WeightedEdge{concrete.Edge{concrete.Node(1), concrete.Node(next + i)}, 0}, - ) - } - return e - }(), - - query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 4}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, - }, - { - name: "zero-weight bi-directional tree with single exit directed", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: func() []concrete.WeightedEdge { - e := []concrete.WeightedEdge{ - // Add a path from 0->4 of weight 4 - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, 1}, - {concrete.Edge{concrete.Node(1), concrete.Node(2)}, 1}, - {concrete.Edge{concrete.Node(2), concrete.Node(3)}, 1}, - {concrete.Edge{concrete.Node(3), concrete.Node(4)}, 1}, - } - - // Make a bi-directional tree rooted at node 2 with - // a single exit to node 4 and co-equal cost from - // 2 to 4. - const ( - depth = 4 - branching = 4 - ) - - next := len(e) + 1 - src := 2 - var i, last int - for l := 0; l < depth; l++ { - for i = 0; i < branching; i++ { - last = next + i - e = append(e, concrete.WeightedEdge{concrete.Edge{concrete.Node(src), concrete.Node(last)}, 0}) - e = append(e, concrete.WeightedEdge{concrete.Edge{concrete.Node(last), concrete.Node(src)}, 0}) - } - src = next + 1 - next += branching - } - e = append(e, concrete.WeightedEdge{concrete.Edge{concrete.Node(last), concrete.Node(4)}, 2}) - return e - }(), - - query: concrete.Edge{concrete.Node(0), concrete.Node(4)}, - weight: 4, - want: [][]int{ - {0, 1, 2, 3, 4}, - {0, 1, 2, 6, 10, 14, 20, 4}, - }, - unique: false, - - none: concrete.Edge{concrete.Node(4), concrete.Node(5)}, - }, - - // Negative weighted graphs. - { - name: "one edge directed negative", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, -1}, - }, - negative: true, - - query: concrete.Edge{concrete.Node(0), concrete.Node(1)}, - weight: -1, - want: [][]int{ - {0, 1}, - }, - unique: true, - - none: concrete.Edge{concrete.Node(2), concrete.Node(3)}, - }, - { - name: "one edge undirected negative", - g: func() graph.Mutable { return concrete.NewGraph() }, - edges: []concrete.WeightedEdge{ - {concrete.Edge{concrete.Node(0), concrete.Node(1)}, -1}, - }, - negative: true, - hasNegativeCycle: true, - - query: concrete.Edge{concrete.Node(0), concrete.Node(1)}, - }, - { - name: "wp graph negative", // http://en.wikipedia.org/w/index.php?title=Johnson%27s_algorithm&oldid=564595231 - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - {concrete.Edge{concrete.Node('w'), concrete.Node('z')}, 2}, - {concrete.Edge{concrete.Node('x'), concrete.Node('w')}, 6}, - {concrete.Edge{concrete.Node('x'), concrete.Node('y')}, 3}, - {concrete.Edge{concrete.Node('y'), concrete.Node('w')}, 4}, - {concrete.Edge{concrete.Node('y'), concrete.Node('z')}, 5}, - {concrete.Edge{concrete.Node('z'), concrete.Node('x')}, -7}, - {concrete.Edge{concrete.Node('z'), concrete.Node('y')}, -3}, - }, - negative: true, - - query: concrete.Edge{concrete.Node('z'), concrete.Node('y')}, - weight: -4, - want: [][]int{ - {'z', 'x', 'y'}, - }, - unique: true, - - none: concrete.Edge{concrete.Node(2), concrete.Node(3)}, - }, - { - name: "roughgarden negative", - g: func() graph.Mutable { return concrete.NewDirectedGraph() }, - edges: []concrete.WeightedEdge{ - {concrete.Edge{concrete.Node('a'), concrete.Node('b')}, -2}, - {concrete.Edge{concrete.Node('b'), concrete.Node('c')}, -1}, - {concrete.Edge{concrete.Node('c'), concrete.Node('a')}, 4}, - {concrete.Edge{concrete.Node('c'), concrete.Node('x')}, 2}, - {concrete.Edge{concrete.Node('c'), concrete.Node('y')}, -3}, - {concrete.Edge{concrete.Node('z'), concrete.Node('x')}, 1}, - {concrete.Edge{concrete.Node('z'), concrete.Node('y')}, -4}, - }, - negative: true, - - query: concrete.Edge{concrete.Node('a'), concrete.Node('y')}, - weight: -6, - want: [][]int{ - {'a', 'b', 'c', 'y'}, - }, - unique: true, - - none: concrete.Edge{concrete.Node(2), concrete.Node(3)}, - }, -} diff --git a/vendor/github.com/gonum/graph/path/spanning_tree.go b/vendor/github.com/gonum/graph/path/spanning_tree.go index 99b30cbc1d48..57a78eb61ce8 100644 --- a/vendor/github.com/gonum/graph/path/spanning_tree.go +++ b/vendor/github.com/gonum/graph/path/spanning_tree.go @@ -5,104 +5,178 @@ package path import ( + "container/heap" + "math" "sort" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" - "github.com/gonum/graph/internal" + "github.com/gonum/graph/simple" ) -// EdgeListerGraph is an undirected graph than returns its complete set of edges. -type EdgeListerGraph interface { +// UndirectedWeighter is an undirected graph that returns edge weights. +type UndirectedWeighter interface { graph.Undirected - Edges() []graph.Edge + graph.Weighter } // Prim generates a minimum spanning tree of g by greedy tree extension, placing -// the result in the destination. The destination is not cleared first. -func Prim(dst graph.MutableUndirected, g EdgeListerGraph) { - var weight graph.WeightFunc - if g, ok := g.(graph.Weighter); ok { - weight = g.Weight - } else { - weight = graph.UniformCost +// the result in the destination, dst. If the edge weights of g are distinct +// it will be the unique minimum spanning tree of g. The destination is not cleared +// first. The weight of the minimum spanning tree is returned. If g is not connected, +// a minimum spanning forest will be constructed in dst and the sum of minimum +// spanning tree weights will be returned. +func Prim(dst graph.UndirectedBuilder, g UndirectedWeighter) float64 { + nodes := g.Nodes() + if len(nodes) == 0 { + return 0 } - nlist := g.Nodes() - - if nlist == nil || len(nlist) == 0 { - return + q := &primQueue{ + indexOf: make(map[int]int, len(nodes)-1), + nodes: make([]simple.Edge, 0, len(nodes)-1), + } + for _, u := range nodes[1:] { + heap.Push(q, simple.Edge{F: u, W: math.Inf(1)}) } - dst.AddNode(nlist[0]) - remainingNodes := make(internal.IntSet) - for _, node := range nlist[1:] { - remainingNodes.Add(node.ID()) + u := nodes[0] + for _, v := range g.From(u) { + w, ok := g.Weight(u, v) + if !ok { + panic("prim: unexpected invalid weight") + } + q.update(v, u, w) } - edgeList := g.Edges() - for remainingNodes.Count() != 0 { - var edges []concrete.WeightedEdge - for _, edge := range edgeList { - if (dst.Has(edge.From()) && remainingNodes.Has(edge.To().ID())) || - (dst.Has(edge.To()) && remainingNodes.Has(edge.From().ID())) { + var w float64 + for q.Len() > 0 { + e := heap.Pop(q).(simple.Edge) + if e.To() != nil && g.HasEdgeBetween(e.From(), e.To()) { + dst.SetEdge(e) + w += e.Weight() + } - edges = append(edges, concrete.WeightedEdge{Edge: edge, Cost: weight(edge)}) + u = e.From() + for _, n := range g.From(u) { + if key, ok := q.key(n); ok { + w, ok := g.Weight(u, n) + if !ok { + panic("prim: unexpected invalid weight") + } + if w < key { + q.update(n, u, w) + } } } + } + return w +} - sort.Sort(byWeight(edges)) - myEdge := edges[0] +// primQueue is a Prim's priority queue. The priority queue is a +// queue of edge From nodes keyed on the minimum edge weight to +// a node in the set of nodes already connected to the minimum +// spanning forest. +type primQueue struct { + indexOf map[int]int + nodes []simple.Edge +} - dst.SetEdge(myEdge.Edge, myEdge.Cost) - remainingNodes.Remove(myEdge.Edge.From().ID()) - } +func (q *primQueue) Less(i, j int) bool { + return q.nodes[i].Weight() < q.nodes[j].Weight() +} + +func (q *primQueue) Swap(i, j int) { + q.indexOf[q.nodes[i].From().ID()] = j + q.indexOf[q.nodes[j].From().ID()] = i + q.nodes[i], q.nodes[j] = q.nodes[j], q.nodes[i] +} + +func (q *primQueue) Len() int { + return len(q.nodes) +} +func (q *primQueue) Push(x interface{}) { + n := x.(simple.Edge) + q.indexOf[n.From().ID()] = len(q.nodes) + q.nodes = append(q.nodes, n) } -// Kruskal generates a minimum spanning tree of g by greedy tree coalesence, placing -// the result in the destination. The destination is not cleared first. -func Kruskal(dst graph.MutableUndirected, g EdgeListerGraph) { - var weight graph.WeightFunc - if g, ok := g.(graph.Weighter); ok { - weight = g.Weight - } else { - weight = graph.UniformCost +func (q *primQueue) Pop() interface{} { + n := q.nodes[len(q.nodes)-1] + q.nodes = q.nodes[:len(q.nodes)-1] + delete(q.indexOf, n.From().ID()) + return n +} + +// key returns the key for the node u and whether the node is +// in the queue. If the node is not in the queue, key is returned +// as +Inf. +func (q *primQueue) key(u graph.Node) (key float64, ok bool) { + i, ok := q.indexOf[u.ID()] + if !ok { + return math.Inf(1), false } + return q.nodes[i].Weight(), ok +} - edgeList := g.Edges() - edges := make([]concrete.WeightedEdge, 0, len(edgeList)) - for _, edge := range edgeList { - edges = append(edges, concrete.WeightedEdge{Edge: edge, Cost: weight(edge)}) +// update updates u's position in the queue with the new closest +// MST-connected neighbour, v, and the key weight between u and v. +func (q *primQueue) update(u, v graph.Node, key float64) { + id := u.ID() + i, ok := q.indexOf[id] + if !ok { + return } + q.nodes[i].T = v + q.nodes[i].W = key + heap.Fix(q, i) +} - sort.Sort(byWeight(edges)) +// UndirectedWeightLister is an undirected graph that returns edge weights and +// the set of edges in the graph. +type UndirectedWeightLister interface { + UndirectedWeighter + Edges() []graph.Edge +} + +// Kruskal generates a minimum spanning tree of g by greedy tree coalescence, placing +// the result in the destination, dst. If the edge weights of g are distinct +// it will be the unique minimum spanning tree of g. The destination is not cleared +// first. The weight of the minimum spanning tree is returned. If g is not connected, +// a minimum spanning forest will be constructed in dst and the sum of minimum +// spanning tree weights will be returned. +func Kruskal(dst graph.UndirectedBuilder, g UndirectedWeightLister) float64 { + edges := g.Edges() + ascend := make([]simple.Edge, 0, len(edges)) + for _, e := range edges { + u := e.From() + v := e.To() + w, ok := g.Weight(u, v) + if !ok { + panic("kruskal: unexpected invalid weight") + } + ascend = append(ascend, simple.Edge{F: u, T: v, W: w}) + } + sort.Sort(byWeight(ascend)) ds := newDisjointSet() for _, node := range g.Nodes() { ds.makeSet(node.ID()) } - for _, edge := range edges { - // The disjoint set doesn't really care for which is head and which is tail so this - // should work fine without checking both ways - if s1, s2 := ds.find(edge.Edge.From().ID()), ds.find(edge.Edge.To().ID()); s1 != s2 { + var w float64 + for _, e := range ascend { + if s1, s2 := ds.find(e.From().ID()), ds.find(e.To().ID()); s1 != s2 { ds.union(s1, s2) - dst.SetEdge(edge.Edge, edge.Cost) + dst.SetEdge(e) + w += e.Weight() } } + return w } -type byWeight []concrete.WeightedEdge - -func (e byWeight) Len() int { - return len(e) -} +type byWeight []simple.Edge -func (e byWeight) Less(i, j int) bool { - return e[i].Cost < e[j].Cost -} - -func (e byWeight) Swap(i, j int) { - e[i], e[j] = e[j], e[i] -} +func (e byWeight) Len() int { return len(e) } +func (e byWeight) Less(i, j int) bool { return e[i].Weight() < e[j].Weight() } +func (e byWeight) Swap(i, j int) { e[i], e[j] = e[j], e[i] } diff --git a/vendor/github.com/gonum/graph/path/spanning_tree_test.go b/vendor/github.com/gonum/graph/path/spanning_tree_test.go new file mode 100644 index 000000000000..49f4fe3b6543 --- /dev/null +++ b/vendor/github.com/gonum/graph/path/spanning_tree_test.go @@ -0,0 +1,294 @@ +// 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 path + +import ( + "fmt" + "math" + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" +) + +func init() { + for _, test := range spanningTreeTests { + var w float64 + for _, e := range test.treeEdges { + w += e.W + } + if w != test.want { + panic(fmt.Sprintf("bad test: %s weight mismatch: %v != %v", test.name, w, test.want)) + } + } +} + +type spanningGraph interface { + graph.UndirectedBuilder + graph.Weighter + Edges() []graph.Edge +} + +var spanningTreeTests = []struct { + name string + graph func() spanningGraph + edges []simple.Edge + want float64 + treeEdges []simple.Edge +}{ + { + name: "Empty", + graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + want: 0, + }, + { + // https://upload.wikimedia.org/wikipedia/commons/f/f7/Prim%27s_algorithm.svg + // Modified to make edge weights unique; A--B is increased to 2.5 otherwise + // to prevent the alternative solution being found. + name: "Prim WP figure 1", + graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + edges: []simple.Edge{ + {F: simple.Node('A'), T: simple.Node('B'), W: 2.5}, + {F: simple.Node('A'), T: simple.Node('D'), W: 1}, + {F: simple.Node('B'), T: simple.Node('D'), W: 2}, + {F: simple.Node('C'), T: simple.Node('D'), W: 3}, + }, + + want: 6, + treeEdges: []simple.Edge{ + {F: simple.Node('A'), T: simple.Node('D'), W: 1}, + {F: simple.Node('B'), T: simple.Node('D'), W: 2}, + {F: simple.Node('C'), T: simple.Node('D'), W: 3}, + }, + }, + { + // https://upload.wikimedia.org/wikipedia/commons/5/5c/MST_kruskal_en.gif + name: "Kruskal WP figure 1", + graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + edges: []simple.Edge{ + {F: simple.Node('a'), T: simple.Node('b'), W: 3}, + {F: simple.Node('a'), T: simple.Node('e'), W: 1}, + {F: simple.Node('b'), T: simple.Node('c'), W: 5}, + {F: simple.Node('b'), T: simple.Node('e'), W: 4}, + {F: simple.Node('c'), T: simple.Node('d'), W: 2}, + {F: simple.Node('c'), T: simple.Node('e'), W: 6}, + {F: simple.Node('d'), T: simple.Node('e'), W: 7}, + }, + + want: 11, + treeEdges: []simple.Edge{ + {F: simple.Node('a'), T: simple.Node('b'), W: 3}, + {F: simple.Node('a'), T: simple.Node('e'), W: 1}, + {F: simple.Node('b'), T: simple.Node('c'), W: 5}, + {F: simple.Node('c'), T: simple.Node('d'), W: 2}, + }, + }, + { + // https://upload.wikimedia.org/wikipedia/commons/8/87/Kruskal_Algorithm_6.svg + name: "Kruskal WP example", + graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + edges: []simple.Edge{ + {F: simple.Node('A'), T: simple.Node('B'), W: 7}, + {F: simple.Node('A'), T: simple.Node('D'), W: 5}, + {F: simple.Node('B'), T: simple.Node('C'), W: 8}, + {F: simple.Node('B'), T: simple.Node('D'), W: 9}, + {F: simple.Node('B'), T: simple.Node('E'), W: 7}, + {F: simple.Node('C'), T: simple.Node('E'), W: 5}, + {F: simple.Node('D'), T: simple.Node('E'), W: 15}, + {F: simple.Node('D'), T: simple.Node('F'), W: 6}, + {F: simple.Node('E'), T: simple.Node('F'), W: 8}, + {F: simple.Node('E'), T: simple.Node('G'), W: 9}, + {F: simple.Node('F'), T: simple.Node('G'), W: 11}, + }, + + want: 39, + treeEdges: []simple.Edge{ + {F: simple.Node('A'), T: simple.Node('B'), W: 7}, + {F: simple.Node('A'), T: simple.Node('D'), W: 5}, + {F: simple.Node('B'), T: simple.Node('E'), W: 7}, + {F: simple.Node('C'), T: simple.Node('E'), W: 5}, + {F: simple.Node('D'), T: simple.Node('F'), W: 6}, + {F: simple.Node('E'), T: simple.Node('G'), W: 9}, + }, + }, + { + // https://upload.wikimedia.org/wikipedia/commons/2/2e/Boruvka%27s_algorithm_%28Sollin%27s_algorithm%29_Anim.gif + name: "Borůvka WP example", + graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + edges: []simple.Edge{ + {F: simple.Node('A'), T: simple.Node('B'), W: 13}, + {F: simple.Node('A'), T: simple.Node('C'), W: 6}, + {F: simple.Node('B'), T: simple.Node('C'), W: 7}, + {F: simple.Node('B'), T: simple.Node('D'), W: 1}, + {F: simple.Node('C'), T: simple.Node('D'), W: 14}, + {F: simple.Node('C'), T: simple.Node('E'), W: 8}, + {F: simple.Node('C'), T: simple.Node('H'), W: 20}, + {F: simple.Node('D'), T: simple.Node('E'), W: 9}, + {F: simple.Node('D'), T: simple.Node('F'), W: 3}, + {F: simple.Node('E'), T: simple.Node('F'), W: 2}, + {F: simple.Node('E'), T: simple.Node('J'), W: 18}, + {F: simple.Node('G'), T: simple.Node('H'), W: 15}, + {F: simple.Node('G'), T: simple.Node('I'), W: 5}, + {F: simple.Node('G'), T: simple.Node('J'), W: 19}, + {F: simple.Node('G'), T: simple.Node('K'), W: 10}, + {F: simple.Node('H'), T: simple.Node('J'), W: 17}, + {F: simple.Node('I'), T: simple.Node('K'), W: 11}, + {F: simple.Node('J'), T: simple.Node('K'), W: 16}, + {F: simple.Node('J'), T: simple.Node('L'), W: 4}, + {F: simple.Node('K'), T: simple.Node('L'), W: 12}, + }, + + want: 83, + treeEdges: []simple.Edge{ + {F: simple.Node('A'), T: simple.Node('C'), W: 6}, + {F: simple.Node('B'), T: simple.Node('C'), W: 7}, + {F: simple.Node('B'), T: simple.Node('D'), W: 1}, + {F: simple.Node('D'), T: simple.Node('F'), W: 3}, + {F: simple.Node('E'), T: simple.Node('F'), W: 2}, + {F: simple.Node('E'), T: simple.Node('J'), W: 18}, + {F: simple.Node('G'), T: simple.Node('H'), W: 15}, + {F: simple.Node('G'), T: simple.Node('I'), W: 5}, + {F: simple.Node('G'), T: simple.Node('K'), W: 10}, + {F: simple.Node('J'), T: simple.Node('L'), W: 4}, + {F: simple.Node('K'), T: simple.Node('L'), W: 12}, + }, + }, + { + // https://upload.wikimedia.org/wikipedia/commons/d/d2/Minimum_spanning_tree.svg + // Nodes labelled row major. + name: "Minimum Spanning Tree WP figure 1", + graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + edges: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(2), W: 4}, + {F: simple.Node(1), T: simple.Node(3), W: 1}, + {F: simple.Node(1), T: simple.Node(4), W: 4}, + {F: simple.Node(2), T: simple.Node(3), W: 5}, + {F: simple.Node(2), T: simple.Node(5), W: 9}, + {F: simple.Node(2), T: simple.Node(6), W: 9}, + {F: simple.Node(2), T: simple.Node(8), W: 7}, + {F: simple.Node(3), T: simple.Node(4), W: 3}, + {F: simple.Node(3), T: simple.Node(8), W: 9}, + {F: simple.Node(4), T: simple.Node(8), W: 10}, + {F: simple.Node(4), T: simple.Node(10), W: 18}, + {F: simple.Node(5), T: simple.Node(6), W: 2}, + {F: simple.Node(5), T: simple.Node(7), W: 4}, + {F: simple.Node(5), T: simple.Node(9), W: 6}, + {F: simple.Node(6), T: simple.Node(7), W: 2}, + {F: simple.Node(6), T: simple.Node(8), W: 8}, + {F: simple.Node(7), T: simple.Node(8), W: 9}, + {F: simple.Node(7), T: simple.Node(9), W: 3}, + {F: simple.Node(7), T: simple.Node(10), W: 9}, + {F: simple.Node(8), T: simple.Node(10), W: 8}, + {F: simple.Node(9), T: simple.Node(10), W: 9}, + }, + + want: 38, + treeEdges: []simple.Edge{ + {F: simple.Node(1), T: simple.Node(2), W: 4}, + {F: simple.Node(1), T: simple.Node(3), W: 1}, + {F: simple.Node(2), T: simple.Node(8), W: 7}, + {F: simple.Node(3), T: simple.Node(4), W: 3}, + {F: simple.Node(5), T: simple.Node(6), W: 2}, + {F: simple.Node(6), T: simple.Node(7), W: 2}, + {F: simple.Node(6), T: simple.Node(8), W: 8}, + {F: simple.Node(7), T: simple.Node(9), W: 3}, + {F: simple.Node(8), T: simple.Node(10), W: 8}, + }, + }, + + { + // https://upload.wikimedia.org/wikipedia/commons/2/2e/Boruvka%27s_algorithm_%28Sollin%27s_algorithm%29_Anim.gif + // but with C--H and E--J cut. + name: "Borůvka WP example cut", + graph: func() spanningGraph { return simple.NewUndirectedGraph(0, math.Inf(1)) }, + edges: []simple.Edge{ + {F: simple.Node('A'), T: simple.Node('B'), W: 13}, + {F: simple.Node('A'), T: simple.Node('C'), W: 6}, + {F: simple.Node('B'), T: simple.Node('C'), W: 7}, + {F: simple.Node('B'), T: simple.Node('D'), W: 1}, + {F: simple.Node('C'), T: simple.Node('D'), W: 14}, + {F: simple.Node('C'), T: simple.Node('E'), W: 8}, + {F: simple.Node('D'), T: simple.Node('E'), W: 9}, + {F: simple.Node('D'), T: simple.Node('F'), W: 3}, + {F: simple.Node('E'), T: simple.Node('F'), W: 2}, + {F: simple.Node('G'), T: simple.Node('H'), W: 15}, + {F: simple.Node('G'), T: simple.Node('I'), W: 5}, + {F: simple.Node('G'), T: simple.Node('J'), W: 19}, + {F: simple.Node('G'), T: simple.Node('K'), W: 10}, + {F: simple.Node('H'), T: simple.Node('J'), W: 17}, + {F: simple.Node('I'), T: simple.Node('K'), W: 11}, + {F: simple.Node('J'), T: simple.Node('K'), W: 16}, + {F: simple.Node('J'), T: simple.Node('L'), W: 4}, + {F: simple.Node('K'), T: simple.Node('L'), W: 12}, + }, + + want: 65, + treeEdges: []simple.Edge{ + {F: simple.Node('A'), T: simple.Node('C'), W: 6}, + {F: simple.Node('B'), T: simple.Node('C'), W: 7}, + {F: simple.Node('B'), T: simple.Node('D'), W: 1}, + {F: simple.Node('D'), T: simple.Node('F'), W: 3}, + {F: simple.Node('E'), T: simple.Node('F'), W: 2}, + {F: simple.Node('G'), T: simple.Node('H'), W: 15}, + {F: simple.Node('G'), T: simple.Node('I'), W: 5}, + {F: simple.Node('G'), T: simple.Node('K'), W: 10}, + {F: simple.Node('J'), T: simple.Node('L'), W: 4}, + {F: simple.Node('K'), T: simple.Node('L'), W: 12}, + }, + }, +} + +func testMinumumSpanning(mst func(dst graph.UndirectedBuilder, g spanningGraph) float64, t *testing.T) { + for _, test := range spanningTreeTests { + g := test.graph() + for _, e := range test.edges { + g.SetEdge(e) + } + + dst := simple.NewUndirectedGraph(0, math.Inf(1)) + w := mst(dst, g) + if w != test.want { + t.Errorf("unexpected minimum spanning tree weight for %q: got: %f want: %f", + test.name, w, test.want) + } + var got float64 + for _, e := range dst.Edges() { + got += e.Weight() + } + if got != test.want { + t.Errorf("unexpected minimum spanning tree edge weight sum for %q: got: %f want: %f", + test.name, got, test.want) + } + + gotEdges := dst.Edges() + if len(gotEdges) != len(test.treeEdges) { + t.Errorf("unexpected number of spanning tree edges for %q: got: %d want: %d", + test.name, len(gotEdges), len(test.treeEdges)) + } + for _, e := range test.treeEdges { + w, ok := dst.Weight(e.From(), e.To()) + if !ok { + t.Errorf("spanning tree edge not found in graph for %q: %+v", + test.name, e) + } + if w != e.Weight() { + t.Errorf("unexpected spanning tree edge weight for %q: got: %f want: %f", + test.name, w, e.Weight()) + } + } + } +} + +func TestKruskal(t *testing.T) { + testMinumumSpanning(func(dst graph.UndirectedBuilder, g spanningGraph) float64 { + return Kruskal(dst, g) + }, t) +} + +func TestPrim(t *testing.T) { + testMinumumSpanning(func(dst graph.UndirectedBuilder, g spanningGraph) float64 { + return Prim(dst, g) + }, t) +} diff --git a/vendor/github.com/gonum/graph/path/weight.go b/vendor/github.com/gonum/graph/path/weight.go new file mode 100644 index 000000000000..cfe1f17f9ac6 --- /dev/null +++ b/vendor/github.com/gonum/graph/path/weight.go @@ -0,0 +1,40 @@ +// 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 path + +import ( + "math" + + "github.com/gonum/graph" +) + +// Weighting is a mapping between a pair of nodes and a weight. It follows the +// semantics of the Weighter interface. +type Weighting func(x, y graph.Node) (w float64, ok bool) + +// UniformCost returns a Weighting that returns an edge cost of 1 for existing +// edges, zero for node identity and Inf for otherwise absent edges. +func UniformCost(g graph.Graph) Weighting { + return func(x, y graph.Node) (w float64, ok bool) { + xid := x.ID() + yid := y.ID() + if xid == yid { + return 0, true + } + if e := g.Edge(x, y); e != nil { + return 1, true + } + return math.Inf(1), false + } +} + +// Heuristic returns an estimate of the cost of travelling between two nodes. +type Heuristic func(x, y graph.Node) float64 + +// HeuristicCoster wraps the HeuristicCost method. A graph implementing the +// interface provides a heuristic between any two given nodes. +type HeuristicCoster interface { + HeuristicCost(x, y graph.Node) float64 +} diff --git a/vendor/github.com/gonum/graph/simple/dense_directed_matrix.go b/vendor/github.com/gonum/graph/simple/dense_directed_matrix.go new file mode 100644 index 000000000000..46db6b0f4655 --- /dev/null +++ b/vendor/github.com/gonum/graph/simple/dense_directed_matrix.go @@ -0,0 +1,265 @@ +// Copyright ©2014 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 simple + +import ( + "sort" + + "github.com/gonum/graph" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/matrix/mat64" +) + +// DirectedMatrix represents a directed graph using an adjacency +// matrix such that all IDs are in a contiguous block from 0 to n-1. +// Edges are stored implicitly as an edge weight, so edges stored in +// the graph are not recoverable. +type DirectedMatrix struct { + mat *mat64.Dense + nodes []graph.Node + + self float64 + absent float64 +} + +// NewDirectedMatrix creates a directed dense graph with n nodes. +// All edges are initialized with the weight given by init. The self parameter +// specifies the cost of self connection, and absent specifies the weight +// returned for absent edges. +func NewDirectedMatrix(n int, init, self, absent float64) *DirectedMatrix { + mat := make([]float64, n*n) + if init != 0 { + for i := range mat { + mat[i] = init + } + } + for i := 0; i < len(mat); i += n + 1 { + mat[i] = self + } + return &DirectedMatrix{ + mat: mat64.NewDense(n, n, mat), + self: self, + absent: absent, + } +} + +// NewDirectedMatrixFrom creates a directed dense graph with the given nodes. +// The IDs of the nodes must be contiguous from 0 to len(nodes)-1, but may +// be in any order. If IDs are not contiguous NewDirectedMatrixFrom will panic. +// All edges are initialized with the weight given by init. The self parameter +// specifies the cost of self connection, and absent specifies the weight +// returned for absent edges. +func NewDirectedMatrixFrom(nodes []graph.Node, init, self, absent float64) *DirectedMatrix { + sort.Sort(ordered.ByID(nodes)) + for i, n := range nodes { + if i != n.ID() { + panic("simple: non-contiguous node IDs") + } + } + g := NewDirectedMatrix(len(nodes), init, self, absent) + g.nodes = nodes + return g +} + +// Node returns the node in the graph with the given ID. +func (g *DirectedMatrix) Node(id int) graph.Node { + if !g.has(id) { + return nil + } + if g.nodes == nil { + return Node(id) + } + return g.nodes[id] +} + +// Has returns whether the node exists within the graph. +func (g *DirectedMatrix) Has(n graph.Node) bool { + return g.has(n.ID()) +} + +func (g *DirectedMatrix) has(id int) bool { + r, _ := g.mat.Dims() + return 0 <= id && id < r +} + +// Nodes returns all the nodes in the graph. +func (g *DirectedMatrix) Nodes() []graph.Node { + if g.nodes != nil { + nodes := make([]graph.Node, len(g.nodes)) + copy(nodes, g.nodes) + return nodes + } + r, _ := g.mat.Dims() + nodes := make([]graph.Node, r) + for i := 0; i < r; i++ { + nodes[i] = Node(i) + } + return nodes +} + +// Edges returns all the edges in the graph. +func (g *DirectedMatrix) Edges() []graph.Edge { + var edges []graph.Edge + r, _ := g.mat.Dims() + for i := 0; i < r; i++ { + for j := 0; j < r; j++ { + if i == j { + continue + } + if w := g.mat.At(i, j); !isSame(w, g.absent) { + edges = append(edges, Edge{F: g.Node(i), T: g.Node(j), W: w}) + } + } + } + return edges +} + +// From returns all nodes in g that can be reached directly from n. +func (g *DirectedMatrix) From(n graph.Node) []graph.Node { + id := n.ID() + if !g.has(id) { + return nil + } + var neighbors []graph.Node + _, c := g.mat.Dims() + for j := 0; j < c; j++ { + if j == id { + continue + } + if !isSame(g.mat.At(id, j), g.absent) { + neighbors = append(neighbors, g.Node(j)) + } + } + return neighbors +} + +// To returns all nodes in g that can reach directly to n. +func (g *DirectedMatrix) To(n graph.Node) []graph.Node { + id := n.ID() + if !g.has(id) { + return nil + } + var neighbors []graph.Node + r, _ := g.mat.Dims() + for i := 0; i < r; i++ { + if i == id { + continue + } + if !isSame(g.mat.At(i, id), g.absent) { + neighbors = append(neighbors, g.Node(i)) + } + } + return neighbors +} + +// HasEdgeBetween returns whether an edge exists between nodes x and y without +// considering direction. +func (g *DirectedMatrix) HasEdgeBetween(x, y graph.Node) bool { + xid := x.ID() + if !g.has(xid) { + return false + } + yid := y.ID() + if !g.has(yid) { + return false + } + return xid != yid && (!isSame(g.mat.At(xid, yid), g.absent) || !isSame(g.mat.At(yid, xid), g.absent)) +} + +// Edge returns the edge from u to v if such an edge exists and nil otherwise. +// The node v must be directly reachable from u as defined by the From method. +func (g *DirectedMatrix) Edge(u, v graph.Node) graph.Edge { + if g.HasEdgeFromTo(u, v) { + return Edge{F: g.Node(u.ID()), T: g.Node(v.ID()), W: g.mat.At(u.ID(), v.ID())} + } + return nil +} + +// HasEdgeFromTo returns whether an edge exists in the graph from u to v. +func (g *DirectedMatrix) HasEdgeFromTo(u, v graph.Node) bool { + uid := u.ID() + if !g.has(uid) { + return false + } + vid := v.ID() + if !g.has(vid) { + return false + } + return uid != vid && !isSame(g.mat.At(uid, vid), g.absent) +} + +// Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge. +// If x and y are the same node or there is no joining edge between the two nodes the weight +// value returned is either the graph's absent or self value. Weight returns true if an edge +// exists between x and y or if x and y have the same ID, false otherwise. +func (g *DirectedMatrix) Weight(x, y graph.Node) (w float64, ok bool) { + xid := x.ID() + yid := y.ID() + if xid == yid { + return g.self, true + } + if g.has(xid) && g.has(yid) { + return g.mat.At(xid, yid), true + } + return g.absent, false +} + +// SetEdge sets e, an edge from one node to another. If the ends of the edge are not in g +// or the edge is a self loop, SetEdge panics. +func (g *DirectedMatrix) SetEdge(e graph.Edge) { + fid := e.From().ID() + tid := e.To().ID() + if fid == tid { + panic("simple: set illegal edge") + } + g.mat.Set(fid, tid, e.Weight()) +} + +// RemoveEdge removes e from the graph, leaving the terminal nodes. If the edge does not exist +// it is a no-op. +func (g *DirectedMatrix) RemoveEdge(e graph.Edge) { + fid := e.From().ID() + if !g.has(fid) { + return + } + tid := e.To().ID() + if !g.has(tid) { + return + } + g.mat.Set(fid, tid, g.absent) +} + +// Degree returns the in+out degree of n in g. +func (g *DirectedMatrix) Degree(n graph.Node) int { + id := n.ID() + var deg int + r, c := g.mat.Dims() + for i := 0; i < r; i++ { + if i == id { + continue + } + if !isSame(g.mat.At(id, i), g.absent) { + deg++ + } + } + for i := 0; i < c; i++ { + if i == id { + continue + } + if !isSame(g.mat.At(i, id), g.absent) { + deg++ + } + } + return deg +} + +// Matrix returns the mat64.Matrix representation of the graph. The orientation +// of the matrix is such that the matrix entry at G_{ij} is the weight of the edge +// from node i to node j. +func (g *DirectedMatrix) Matrix() mat64.Matrix { + // Prevent alteration of dimensions of the returned matrix. + m := *g.mat + return &m +} diff --git a/vendor/github.com/gonum/graph/simple/dense_undirected_matrix.go b/vendor/github.com/gonum/graph/simple/dense_undirected_matrix.go new file mode 100644 index 000000000000..63f20a48f085 --- /dev/null +++ b/vendor/github.com/gonum/graph/simple/dense_undirected_matrix.go @@ -0,0 +1,224 @@ +// Copyright ©2014 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 simple + +import ( + "sort" + + "github.com/gonum/graph" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/matrix/mat64" +) + +// UndirectedMatrix represents an undirected graph using an adjacency +// matrix such that all IDs are in a contiguous block from 0 to n-1. +// Edges are stored implicitly as an edge weight, so edges stored in +// the graph are not recoverable. +type UndirectedMatrix struct { + mat *mat64.SymDense + nodes []graph.Node + + self float64 + absent float64 +} + +// NewUndirectedMatrix creates an undirected dense graph with n nodes. +// All edges are initialized with the weight given by init. The self parameter +// specifies the cost of self connection, and absent specifies the weight +// returned for absent edges. +func NewUndirectedMatrix(n int, init, self, absent float64) *UndirectedMatrix { + mat := make([]float64, n*n) + if init != 0 { + for i := range mat { + mat[i] = init + } + } + for i := 0; i < len(mat); i += n + 1 { + mat[i] = self + } + return &UndirectedMatrix{ + mat: mat64.NewSymDense(n, mat), + self: self, + absent: absent, + } +} + +// NewUndirectedMatrixFrom creates an undirected dense graph with the given nodes. +// The IDs of the nodes must be contiguous from 0 to len(nodes)-1, but may +// be in any order. If IDs are not contiguous NewUndirectedMatrixFrom will panic. +// All edges are initialized with the weight given by init. The self parameter +// specifies the cost of self connection, and absent specifies the weight +// returned for absent edges. +func NewUndirectedMatrixFrom(nodes []graph.Node, init, self, absent float64) *UndirectedMatrix { + sort.Sort(ordered.ByID(nodes)) + for i, n := range nodes { + if i != n.ID() { + panic("simple: non-contiguous node IDs") + } + } + g := NewUndirectedMatrix(len(nodes), init, self, absent) + g.nodes = nodes + return g +} + +// Node returns the node in the graph with the given ID. +func (g *UndirectedMatrix) Node(id int) graph.Node { + if !g.has(id) { + return nil + } + if g.nodes == nil { + return Node(id) + } + return g.nodes[id] +} + +// Has returns whether the node exists within the graph. +func (g *UndirectedMatrix) Has(n graph.Node) bool { + return g.has(n.ID()) +} + +func (g *UndirectedMatrix) has(id int) bool { + r := g.mat.Symmetric() + return 0 <= id && id < r +} + +// Nodes returns all the nodes in the graph. +func (g *UndirectedMatrix) Nodes() []graph.Node { + if g.nodes != nil { + nodes := make([]graph.Node, len(g.nodes)) + copy(nodes, g.nodes) + return nodes + } + r := g.mat.Symmetric() + nodes := make([]graph.Node, r) + for i := 0; i < r; i++ { + nodes[i] = Node(i) + } + return nodes +} + +// Edges returns all the edges in the graph. +func (g *UndirectedMatrix) Edges() []graph.Edge { + var edges []graph.Edge + r, _ := g.mat.Dims() + for i := 0; i < r; i++ { + for j := i + 1; j < r; j++ { + if w := g.mat.At(i, j); !isSame(w, g.absent) { + edges = append(edges, Edge{F: g.Node(i), T: g.Node(j), W: w}) + } + } + } + return edges +} + +// From returns all nodes in g that can be reached directly from n. +func (g *UndirectedMatrix) From(n graph.Node) []graph.Node { + id := n.ID() + if !g.has(id) { + return nil + } + var neighbors []graph.Node + r := g.mat.Symmetric() + for i := 0; i < r; i++ { + if i == id { + continue + } + if !isSame(g.mat.At(id, i), g.absent) { + neighbors = append(neighbors, g.Node(i)) + } + } + return neighbors +} + +// HasEdgeBetween returns whether an edge exists between nodes x and y. +func (g *UndirectedMatrix) HasEdgeBetween(u, v graph.Node) bool { + uid := u.ID() + if !g.has(uid) { + return false + } + vid := v.ID() + if !g.has(vid) { + return false + } + return uid != vid && !isSame(g.mat.At(uid, vid), g.absent) +} + +// Edge returns the edge from u to v if such an edge exists and nil otherwise. +// The node v must be directly reachable from u as defined by the From method. +func (g *UndirectedMatrix) Edge(u, v graph.Node) graph.Edge { + return g.EdgeBetween(u, v) +} + +// EdgeBetween returns the edge between nodes x and y. +func (g *UndirectedMatrix) EdgeBetween(u, v graph.Node) graph.Edge { + if g.HasEdgeBetween(u, v) { + return Edge{F: g.Node(u.ID()), T: g.Node(v.ID()), W: g.mat.At(u.ID(), v.ID())} + } + return nil +} + +// Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge. +// If x and y are the same node or there is no joining edge between the two nodes the weight +// value returned is either the graph's absent or self value. Weight returns true if an edge +// exists between x and y or if x and y have the same ID, false otherwise. +func (g *UndirectedMatrix) Weight(x, y graph.Node) (w float64, ok bool) { + xid := x.ID() + yid := y.ID() + if xid == yid { + return g.self, true + } + if g.has(xid) && g.has(yid) { + return g.mat.At(xid, yid), true + } + return g.absent, false +} + +// SetEdge sets e, an edge from one node to another. If the ends of the edge are not in g +// or the edge is a self loop, SetEdge panics. +func (g *UndirectedMatrix) SetEdge(e graph.Edge) { + fid := e.From().ID() + tid := e.To().ID() + if fid == tid { + panic("simple: set illegal edge") + } + g.mat.SetSym(fid, tid, e.Weight()) +} + +// RemoveEdge removes e from the graph, leaving the terminal nodes. If the edge does not exist +// it is a no-op. +func (g *UndirectedMatrix) RemoveEdge(e graph.Edge) { + fid := e.From().ID() + if !g.has(fid) { + return + } + tid := e.To().ID() + if !g.has(tid) { + return + } + g.mat.SetSym(fid, tid, g.absent) +} + +// Degree returns the degree of n in g. +func (g *UndirectedMatrix) Degree(n graph.Node) int { + id := n.ID() + var deg int + r := g.mat.Symmetric() + for i := 0; i < r; i++ { + if i == id { + continue + } + if !isSame(g.mat.At(id, i), g.absent) { + deg++ + } + } + return deg +} + +// Matrix returns the mat64.Matrix representation of the graph. +func (g *UndirectedMatrix) Matrix() mat64.Matrix { + // Prevent alteration of dimensions of the returned matrix. + m := *g.mat + return &m +} diff --git a/vendor/github.com/gonum/graph/simple/densegraph_test.go b/vendor/github.com/gonum/graph/simple/densegraph_test.go new file mode 100644 index 000000000000..d68fd1d88f6d --- /dev/null +++ b/vendor/github.com/gonum/graph/simple/densegraph_test.go @@ -0,0 +1,140 @@ +// Copyright ©2014 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 simple + +import ( + "math" + "sort" + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/internal/ordered" +) + +var ( + _ graph.Graph = (*UndirectedMatrix)(nil) + _ graph.Directed = (*DirectedMatrix)(nil) +) + +func TestBasicDenseImpassable(t *testing.T) { + dg := NewUndirectedMatrix(5, math.Inf(1), 0, math.Inf(1)) + if dg == nil { + t.Fatal("Directed graph could not be made") + } + + for i := 0; i < 5; i++ { + if !dg.Has(Node(i)) { + t.Errorf("Node that should exist doesn't: %d", i) + } + + if degree := dg.Degree(Node(i)); degree != 0 { + t.Errorf("Node in impassable graph has a neighbor. Node: %d Degree: %d", i, degree) + } + } + + for i := 5; i < 10; i++ { + if dg.Has(Node(i)) { + t.Errorf("Node exists that shouldn't: %d", i) + } + } +} + +func TestBasicDensePassable(t *testing.T) { + dg := NewUndirectedMatrix(5, 1, 0, math.Inf(1)) + if dg == nil { + t.Fatal("Directed graph could not be made") + } + + for i := 0; i < 5; i++ { + if !dg.Has(Node(i)) { + t.Errorf("Node that should exist doesn't: %d", i) + } + + if degree := dg.Degree(Node(i)); degree != 4 { + t.Errorf("Node in passable graph missing neighbors. Node: %d Degree: %d", i, degree) + } + } + + for i := 5; i < 10; i++ { + if dg.Has(Node(i)) { + t.Errorf("Node exists that shouldn't: %d", i) + } + } +} + +func TestDirectedDenseAddRemove(t *testing.T) { + dg := NewDirectedMatrix(10, math.Inf(1), 0, math.Inf(1)) + dg.SetEdge(Edge{F: Node(0), T: Node(2), W: 1}) + + if neighbors := dg.From(Node(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 || + dg.Edge(Node(0), Node(2)) == nil { + t.Errorf("Adding edge didn't create successor") + } + + dg.RemoveEdge(Edge{F: Node(0), T: Node(2)}) + + if neighbors := dg.From(Node(0)); len(neighbors) != 0 || dg.Edge(Node(0), Node(2)) != nil { + t.Errorf("Removing edge didn't properly remove successor") + } + + if neighbors := dg.To(Node(2)); len(neighbors) != 0 || dg.Edge(Node(0), Node(2)) != nil { + t.Errorf("Removing directed edge wrongly kept predecessor") + } + + dg.SetEdge(Edge{F: Node(0), T: Node(2), W: 2}) + // I figure we've torture tested From/To at this point + // so we'll just use the bool functions now + if dg.Edge(Node(0), Node(2)) == nil { + t.Fatal("Adding directed edge didn't change successor back") + } + c1, _ := dg.Weight(Node(2), Node(0)) + c2, _ := dg.Weight(Node(0), Node(2)) + if c1 == c2 { + t.Error("Adding directed edge affected cost in undirected manner") + } +} + +func TestUndirectedDenseAddRemove(t *testing.T) { + dg := NewUndirectedMatrix(10, math.Inf(1), 0, math.Inf(1)) + dg.SetEdge(Edge{F: Node(0), T: Node(2)}) + + if neighbors := dg.From(Node(0)); len(neighbors) != 1 || neighbors[0].ID() != 2 || + dg.EdgeBetween(Node(0), Node(2)) == nil { + t.Errorf("Couldn't add neighbor") + } + + if neighbors := dg.From(Node(2)); len(neighbors) != 1 || neighbors[0].ID() != 0 || + dg.EdgeBetween(Node(2), Node(0)) == nil { + t.Errorf("Adding an undirected neighbor didn't add it reciprocally") + } +} + +func TestDenseLists(t *testing.T) { + dg := NewDirectedMatrix(15, 1, 0, math.Inf(1)) + nodes := dg.Nodes() + + if len(nodes) != 15 { + t.Fatalf("Wrong number of nodes") + } + + sort.Sort(ordered.ByID(nodes)) + + for i, node := range dg.Nodes() { + if i != node.ID() { + t.Errorf("Node list doesn't return properly id'd nodes") + } + } + + edges := dg.Edges() + if len(edges) != 15*14 { + t.Errorf("Improper number of edges for passable dense graph") + } + + dg.RemoveEdge(Edge{F: Node(12), T: Node(11)}) + edges = dg.Edges() + if len(edges) != (15*14)-1 { + t.Errorf("Removing edge didn't affect edge listing properly") + } +} diff --git a/vendor/github.com/gonum/graph/simple/directed.go b/vendor/github.com/gonum/graph/simple/directed.go new file mode 100644 index 000000000000..fd67caded6d7 --- /dev/null +++ b/vendor/github.com/gonum/graph/simple/directed.go @@ -0,0 +1,280 @@ +// Copyright ©2014 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 simple + +import ( + "fmt" + + "golang.org/x/tools/container/intsets" + + "github.com/gonum/graph" +) + +// DirectedGraph implements a generalized directed graph. +type DirectedGraph struct { + nodes map[int]graph.Node + from map[int]map[int]graph.Edge + to map[int]map[int]graph.Edge + + self, absent float64 + + freeIDs intsets.Sparse + usedIDs intsets.Sparse +} + +// NewDirectedGraph returns a DirectedGraph with the specified self and absent +// edge weight values. +func NewDirectedGraph(self, absent float64) *DirectedGraph { + return &DirectedGraph{ + nodes: make(map[int]graph.Node), + from: make(map[int]map[int]graph.Edge), + to: make(map[int]map[int]graph.Edge), + + self: self, + absent: absent, + } +} + +// NewNodeID returns a new unique ID for a node to be added to g. The returned ID does +// not become a valid ID in g until it is added to g. +func (g *DirectedGraph) NewNodeID() int { + if len(g.nodes) == 0 { + return 0 + } + if len(g.nodes) == maxInt { + panic(fmt.Sprintf("simple: cannot allocate node: no slot")) + } + + var id int + if g.freeIDs.Len() != 0 && g.freeIDs.TakeMin(&id) { + return id + } + if id = g.usedIDs.Max(); id < maxInt { + return id + 1 + } + for id = 0; id < maxInt; id++ { + if !g.usedIDs.Has(id) { + return id + } + } + panic("unreachable") +} + +// AddNode adds n to the graph. It panics if the added node ID matches an existing node ID. +func (g *DirectedGraph) AddNode(n graph.Node) { + if _, exists := g.nodes[n.ID()]; exists { + panic(fmt.Sprintf("simple: node ID collision: %d", n.ID())) + } + g.nodes[n.ID()] = n + g.from[n.ID()] = make(map[int]graph.Edge) + g.to[n.ID()] = make(map[int]graph.Edge) + + g.freeIDs.Remove(n.ID()) + g.usedIDs.Insert(n.ID()) +} + +// RemoveNode removes n from the graph, as well as any edges attached to it. If the node +// is not in the graph it is a no-op. +func (g *DirectedGraph) RemoveNode(n graph.Node) { + if _, ok := g.nodes[n.ID()]; !ok { + return + } + delete(g.nodes, n.ID()) + + for from := range g.from[n.ID()] { + delete(g.to[from], n.ID()) + } + delete(g.from, n.ID()) + + for to := range g.to[n.ID()] { + delete(g.from[to], n.ID()) + } + delete(g.to, n.ID()) + + g.freeIDs.Insert(n.ID()) + g.usedIDs.Remove(n.ID()) +} + +// SetEdge adds e, an edge from one node to another. If the nodes do not exist, they are added. +// It will panic if the IDs of the e.From and e.To are equal. +func (g *DirectedGraph) SetEdge(e graph.Edge) { + var ( + from = e.From() + fid = from.ID() + to = e.To() + tid = to.ID() + ) + + if fid == tid { + panic("simple: adding self edge") + } + + if !g.Has(from) { + g.AddNode(from) + } + if !g.Has(to) { + g.AddNode(to) + } + + g.from[fid][tid] = e + g.to[tid][fid] = e +} + +// RemoveEdge removes e from the graph, leaving the terminal nodes. If the edge does not exist +// it is a no-op. +func (g *DirectedGraph) RemoveEdge(e graph.Edge) { + from, to := e.From(), e.To() + if _, ok := g.nodes[from.ID()]; !ok { + return + } + if _, ok := g.nodes[to.ID()]; !ok { + return + } + + delete(g.from[from.ID()], to.ID()) + delete(g.to[to.ID()], from.ID()) +} + +// Node returns the node in the graph with the given ID. +func (g *DirectedGraph) Node(id int) graph.Node { + return g.nodes[id] +} + +// Has returns whether the node exists within the graph. +func (g *DirectedGraph) Has(n graph.Node) bool { + _, ok := g.nodes[n.ID()] + + return ok +} + +// Nodes returns all the nodes in the graph. +func (g *DirectedGraph) Nodes() []graph.Node { + nodes := make([]graph.Node, len(g.from)) + i := 0 + for _, n := range g.nodes { + nodes[i] = n + i++ + } + + return nodes +} + +// Edges returns all the edges in the graph. +func (g *DirectedGraph) Edges() []graph.Edge { + var edges []graph.Edge + for _, u := range g.nodes { + for _, e := range g.from[u.ID()] { + edges = append(edges, e) + } + } + return edges +} + +// From returns all nodes in g that can be reached directly from n. +func (g *DirectedGraph) From(n graph.Node) []graph.Node { + if _, ok := g.from[n.ID()]; !ok { + return nil + } + + from := make([]graph.Node, len(g.from[n.ID()])) + i := 0 + for id := range g.from[n.ID()] { + from[i] = g.nodes[id] + i++ + } + + return from +} + +// To returns all nodes in g that can reach directly to n. +func (g *DirectedGraph) To(n graph.Node) []graph.Node { + if _, ok := g.from[n.ID()]; !ok { + return nil + } + + to := make([]graph.Node, len(g.to[n.ID()])) + i := 0 + for id := range g.to[n.ID()] { + to[i] = g.nodes[id] + i++ + } + + return to +} + +// HasEdgeBetween returns whether an edge exists between nodes x and y without +// considering direction. +func (g *DirectedGraph) HasEdgeBetween(x, y graph.Node) bool { + xid := x.ID() + yid := y.ID() + if _, ok := g.nodes[xid]; !ok { + return false + } + if _, ok := g.nodes[yid]; !ok { + return false + } + if _, ok := g.from[xid][yid]; ok { + return true + } + _, ok := g.from[yid][xid] + return ok +} + +// Edge returns the edge from u to v if such an edge exists and nil otherwise. +// The node v must be directly reachable from u as defined by the From method. +func (g *DirectedGraph) Edge(u, v graph.Node) graph.Edge { + if _, ok := g.nodes[u.ID()]; !ok { + return nil + } + if _, ok := g.nodes[v.ID()]; !ok { + return nil + } + edge, ok := g.from[u.ID()][v.ID()] + if !ok { + return nil + } + return edge +} + +// HasEdgeFromTo returns whether an edge exists in the graph from u to v. +func (g *DirectedGraph) HasEdgeFromTo(u, v graph.Node) bool { + if _, ok := g.nodes[u.ID()]; !ok { + return false + } + if _, ok := g.nodes[v.ID()]; !ok { + return false + } + if _, ok := g.from[u.ID()][v.ID()]; !ok { + return false + } + return true +} + +// Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge. +// If x and y are the same node or there is no joining edge between the two nodes the weight +// value returned is either the graph's absent or self value. Weight returns true if an edge +// exists between x and y or if x and y have the same ID, false otherwise. +func (g *DirectedGraph) Weight(x, y graph.Node) (w float64, ok bool) { + xid := x.ID() + yid := y.ID() + if xid == yid { + return g.self, true + } + if to, ok := g.from[xid]; ok { + if e, ok := to[yid]; ok { + return e.Weight(), true + } + } + return g.absent, false +} + +// Degree returns the in+out degree of n in g. +func (g *DirectedGraph) Degree(n graph.Node) int { + if _, ok := g.nodes[n.ID()]; !ok { + return 0 + } + + return len(g.from[n.ID()]) + len(g.to[n.ID()]) +} diff --git a/vendor/github.com/gonum/graph/simple/directed_test.go b/vendor/github.com/gonum/graph/simple/directed_test.go new file mode 100644 index 000000000000..c685a0502984 --- /dev/null +++ b/vendor/github.com/gonum/graph/simple/directed_test.go @@ -0,0 +1,63 @@ +// Copyright ©2014 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 simple + +import ( + "math" + "testing" + + "github.com/gonum/graph" +) + +var _ graph.Graph = &DirectedGraph{} +var _ graph.Directed = &DirectedGraph{} +var _ graph.Directed = &DirectedGraph{} + +// Tests Issue #27 +func TestEdgeOvercounting(t *testing.T) { + g := generateDummyGraph() + + if neigh := g.From(Node(Node(2))); len(neigh) != 2 { + t.Errorf("Node 2 has incorrect number of neighbors got neighbors %v (count %d), expected 2 neighbors {0,1}", neigh, len(neigh)) + } +} + +func generateDummyGraph() *DirectedGraph { + nodes := [4]struct{ srcID, targetID int }{ + {2, 1}, + {1, 0}, + {2, 0}, + {0, 2}, + } + + g := NewDirectedGraph(0, math.Inf(1)) + + for _, n := range nodes { + g.SetEdge(Edge{F: Node(n.srcID), T: Node(n.targetID), W: 1}) + } + + return g +} + +// Test for issue #123 https://github.com/gonum/graph/issues/123 +func TestIssue123DirectedGraph(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("unexpected panic: %v", r) + } + }() + g := NewDirectedGraph(0, math.Inf(1)) + + n0 := Node(g.NewNodeID()) + g.AddNode(n0) + + n1 := Node(g.NewNodeID()) + g.AddNode(n1) + + g.RemoveNode(n0) + + n2 := Node(g.NewNodeID()) + g.AddNode(n2) +} diff --git a/vendor/github.com/gonum/graph/simple/simple.go b/vendor/github.com/gonum/graph/simple/simple.go new file mode 100644 index 000000000000..ab9fff0b8334 --- /dev/null +++ b/vendor/github.com/gonum/graph/simple/simple.go @@ -0,0 +1,45 @@ +// Copyright ©2014 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 simple provides a suite of simple graph implementations satisfying +// the gonum/graph interfaces. +package simple + +import ( + "math" + + "github.com/gonum/graph" +) + +// Node is a simple graph node. +type Node int + +// ID returns the ID number of the node. +func (n Node) ID() int { + return int(n) +} + +// Edge is a simple graph edge. +type Edge struct { + F, T graph.Node + W float64 +} + +// From returns the from-node of the edge. +func (e Edge) From() graph.Node { return e.F } + +// To returns the to-node of the edge. +func (e Edge) To() graph.Node { return e.T } + +// Weight returns the weight of the edge. +func (e Edge) Weight() float64 { return e.W } + +// maxInt is the maximum value of the machine-dependent int type. +const maxInt int = int(^uint(0) >> 1) + +// isSame returns whether two float64 values are the same where NaN values +// are equalable. +func isSame(a, b float64) bool { + return a == b || (math.IsNaN(a) && math.IsNaN(b)) +} diff --git a/vendor/github.com/gonum/graph/simple/undirected.go b/vendor/github.com/gonum/graph/simple/undirected.go new file mode 100644 index 000000000000..67154b50190a --- /dev/null +++ b/vendor/github.com/gonum/graph/simple/undirected.go @@ -0,0 +1,241 @@ +// Copyright ©2014 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 simple + +import ( + "fmt" + + "golang.org/x/tools/container/intsets" + + "github.com/gonum/graph" +) + +// UndirectedGraph implements a generalized undirected graph. +type UndirectedGraph struct { + nodes map[int]graph.Node + edges map[int]map[int]graph.Edge + + self, absent float64 + + freeIDs intsets.Sparse + usedIDs intsets.Sparse +} + +// NewUndirectedGraph returns an UndirectedGraph with the specified self and absent +// edge weight values. +func NewUndirectedGraph(self, absent float64) *UndirectedGraph { + return &UndirectedGraph{ + nodes: make(map[int]graph.Node), + edges: make(map[int]map[int]graph.Edge), + + self: self, + absent: absent, + } +} + +// NewNodeID returns a new unique ID for a node to be added to g. The returned ID does +// not become a valid ID in g until it is added to g. +func (g *UndirectedGraph) NewNodeID() int { + if len(g.nodes) == 0 { + return 0 + } + if len(g.nodes) == maxInt { + panic(fmt.Sprintf("simple: cannot allocate node: no slot")) + } + + var id int + if g.freeIDs.Len() != 0 && g.freeIDs.TakeMin(&id) { + return id + } + if id = g.usedIDs.Max(); id < maxInt { + return id + 1 + } + for id = 0; id < maxInt; id++ { + if !g.usedIDs.Has(id) { + return id + } + } + panic("unreachable") +} + +// AddNode adds n to the graph. It panics if the added node ID matches an existing node ID. +func (g *UndirectedGraph) AddNode(n graph.Node) { + if _, exists := g.nodes[n.ID()]; exists { + panic(fmt.Sprintf("simple: node ID collision: %d", n.ID())) + } + g.nodes[n.ID()] = n + g.edges[n.ID()] = make(map[int]graph.Edge) + + g.freeIDs.Remove(n.ID()) + g.usedIDs.Insert(n.ID()) +} + +// RemoveNode removes n from the graph, as well as any edges attached to it. If the node +// is not in the graph it is a no-op. +func (g *UndirectedGraph) RemoveNode(n graph.Node) { + if _, ok := g.nodes[n.ID()]; !ok { + return + } + delete(g.nodes, n.ID()) + + for from := range g.edges[n.ID()] { + delete(g.edges[from], n.ID()) + } + delete(g.edges, n.ID()) + + g.freeIDs.Insert(n.ID()) + g.usedIDs.Remove(n.ID()) + +} + +// SetEdge adds e, an edge from one node to another. If the nodes do not exist, they are added. +// It will panic if the IDs of the e.From and e.To are equal. +func (g *UndirectedGraph) SetEdge(e graph.Edge) { + var ( + from = e.From() + fid = from.ID() + to = e.To() + tid = to.ID() + ) + + if fid == tid { + panic("simple: adding self edge") + } + + if !g.Has(from) { + g.AddNode(from) + } + if !g.Has(to) { + g.AddNode(to) + } + + g.edges[fid][tid] = e + g.edges[tid][fid] = e +} + +// RemoveEdge removes e from the graph, leaving the terminal nodes. If the edge does not exist +// it is a no-op. +func (g *UndirectedGraph) RemoveEdge(e graph.Edge) { + from, to := e.From(), e.To() + if _, ok := g.nodes[from.ID()]; !ok { + return + } + if _, ok := g.nodes[to.ID()]; !ok { + return + } + + delete(g.edges[from.ID()], to.ID()) + delete(g.edges[to.ID()], from.ID()) +} + +// Node returns the node in the graph with the given ID. +func (g *UndirectedGraph) Node(id int) graph.Node { + return g.nodes[id] +} + +// Has returns whether the node exists within the graph. +func (g *UndirectedGraph) Has(n graph.Node) bool { + _, ok := g.nodes[n.ID()] + return ok +} + +// Nodes returns all the nodes in the graph. +func (g *UndirectedGraph) Nodes() []graph.Node { + nodes := make([]graph.Node, len(g.nodes)) + i := 0 + for _, n := range g.nodes { + nodes[i] = n + i++ + } + + return nodes +} + +// Edges returns all the edges in the graph. +func (g *UndirectedGraph) Edges() []graph.Edge { + var edges []graph.Edge + + seen := make(map[[2]int]struct{}) + for _, u := range g.edges { + for _, e := range u { + uid := e.From().ID() + vid := e.To().ID() + if _, ok := seen[[2]int{uid, vid}]; ok { + continue + } + seen[[2]int{uid, vid}] = struct{}{} + seen[[2]int{vid, uid}] = struct{}{} + edges = append(edges, e) + } + } + + return edges +} + +// From returns all nodes in g that can be reached directly from n. +func (g *UndirectedGraph) From(n graph.Node) []graph.Node { + if !g.Has(n) { + return nil + } + + nodes := make([]graph.Node, len(g.edges[n.ID()])) + i := 0 + for from := range g.edges[n.ID()] { + nodes[i] = g.nodes[from] + i++ + } + + return nodes +} + +// HasEdgeBetween returns whether an edge exists between nodes x and y. +func (g *UndirectedGraph) HasEdgeBetween(x, y graph.Node) bool { + _, ok := g.edges[x.ID()][y.ID()] + return ok +} + +// Edge returns the edge from u to v if such an edge exists and nil otherwise. +// The node v must be directly reachable from u as defined by the From method. +func (g *UndirectedGraph) Edge(u, v graph.Node) graph.Edge { + return g.EdgeBetween(u, v) +} + +// EdgeBetween returns the edge between nodes x and y. +func (g *UndirectedGraph) EdgeBetween(x, y graph.Node) graph.Edge { + // We don't need to check if neigh exists because + // it's implicit in the edges access. + if !g.Has(x) { + return nil + } + + return g.edges[x.ID()][y.ID()] +} + +// Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge. +// If x and y are the same node or there is no joining edge between the two nodes the weight +// value returned is either the graph's absent or self value. Weight returns true if an edge +// exists between x and y or if x and y have the same ID, false otherwise. +func (g *UndirectedGraph) Weight(x, y graph.Node) (w float64, ok bool) { + xid := x.ID() + yid := y.ID() + if xid == yid { + return g.self, true + } + if n, ok := g.edges[xid]; ok { + if e, ok := n[yid]; ok { + return e.Weight(), true + } + } + return g.absent, false +} + +// Degree returns the degree of n in g. +func (g *UndirectedGraph) Degree(n graph.Node) int { + if _, ok := g.nodes[n.ID()]; !ok { + return 0 + } + + return len(g.edges[n.ID()]) +} diff --git a/vendor/github.com/gonum/graph/simple/undirected_test.go b/vendor/github.com/gonum/graph/simple/undirected_test.go new file mode 100644 index 000000000000..1aee3e2827fd --- /dev/null +++ b/vendor/github.com/gonum/graph/simple/undirected_test.go @@ -0,0 +1,63 @@ +// Copyright ©2014 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 simple + +import ( + "math" + "testing" + + "github.com/gonum/graph" +) + +var _ graph.Graph = (*UndirectedGraph)(nil) + +func TestAssertMutableNotDirected(t *testing.T) { + var g graph.UndirectedBuilder = NewUndirectedGraph(0, math.Inf(1)) + if _, ok := g.(graph.Directed); ok { + t.Fatal("Graph is directed, but a MutableGraph cannot safely be directed!") + } +} + +func TestMaxID(t *testing.T) { + g := NewUndirectedGraph(0, math.Inf(1)) + nodes := make(map[graph.Node]struct{}) + for i := Node(0); i < 3; i++ { + g.AddNode(i) + nodes[i] = struct{}{} + } + g.RemoveNode(Node(0)) + delete(nodes, Node(0)) + g.RemoveNode(Node(2)) + delete(nodes, Node(2)) + n := Node(g.NewNodeID()) + g.AddNode(n) + if !g.Has(n) { + t.Error("added node does not exist in graph") + } + if _, exists := nodes[n]; exists { + t.Errorf("Created already existing node id: %v", n.ID()) + } +} + +// Test for issue #123 https://github.com/gonum/graph/issues/123 +func TestIssue123UndirectedGraph(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("unexpected panic: %v", r) + } + }() + g := NewUndirectedGraph(0, math.Inf(1)) + + n0 := Node(g.NewNodeID()) + g.AddNode(n0) + + n1 := Node(g.NewNodeID()) + g.AddNode(n1) + + g.RemoveNode(n0) + + n2 := Node(g.NewNodeID()) + g.AddNode(n2) +} diff --git a/vendor/github.com/gonum/graph/topo/bench_test.go b/vendor/github.com/gonum/graph/topo/bench_test.go new file mode 100644 index 000000000000..722b13e2174e --- /dev/null +++ b/vendor/github.com/gonum/graph/topo/bench_test.go @@ -0,0 +1,58 @@ +// 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 topo + +import ( + "math" + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/graphs/gen" + "github.com/gonum/graph/simple" +) + +var ( + gnpDirected_10_tenth = gnpDirected(10, 0.1) + gnpDirected_100_tenth = gnpDirected(100, 0.1) + gnpDirected_1000_tenth = gnpDirected(1000, 0.1) + gnpDirected_10_half = gnpDirected(10, 0.5) + gnpDirected_100_half = gnpDirected(100, 0.5) + gnpDirected_1000_half = gnpDirected(1000, 0.5) +) + +func gnpDirected(n int, p float64) graph.Directed { + g := simple.NewDirectedGraph(0, math.Inf(1)) + gen.Gnp(g, n, p, nil) + return g +} + +func benchmarkTarjanSCC(b *testing.B, g graph.Directed) { + var sccs [][]graph.Node + for i := 0; i < b.N; i++ { + sccs = TarjanSCC(g) + } + if len(sccs) == 0 { + b.Fatal("unexpected number zero-sized SCC set") + } +} + +func BenchmarkTarjanSCCGnp_10_tenth(b *testing.B) { + benchmarkTarjanSCC(b, gnpDirected_10_tenth) +} +func BenchmarkTarjanSCCGnp_100_tenth(b *testing.B) { + benchmarkTarjanSCC(b, gnpDirected_100_tenth) +} +func BenchmarkTarjanSCCGnp_1000_tenth(b *testing.B) { + benchmarkTarjanSCC(b, gnpDirected_1000_tenth) +} +func BenchmarkTarjanSCCGnp_10_half(b *testing.B) { + benchmarkTarjanSCC(b, gnpDirected_10_half) +} +func BenchmarkTarjanSCCGnp_100_half(b *testing.B) { + benchmarkTarjanSCC(b, gnpDirected_100_half) +} +func BenchmarkTarjanSCCGnp_1000_half(b *testing.B) { + benchmarkTarjanSCC(b, gnpDirected_1000_half) +} diff --git a/vendor/github.com/gonum/graph/topo/bron_kerbosch.go b/vendor/github.com/gonum/graph/topo/bron_kerbosch.go index 5e30d5bbab80..c2ec7f9b67d5 100644 --- a/vendor/github.com/gonum/graph/topo/bron_kerbosch.go +++ b/vendor/github.com/gonum/graph/topo/bron_kerbosch.go @@ -6,7 +6,7 @@ package topo import ( "github.com/gonum/graph" - "github.com/gonum/graph/internal" + "github.com/gonum/graph/internal/set" ) // VertexOrdering returns the vertex ordering and the k-cores of @@ -49,7 +49,7 @@ func VertexOrdering(g graph.Undirected) (order []graph.Node, cores [][]graph.Nod k := 0 // Repeat n times: s := []int{0} - for _ = range nodes { // TODO(kortschak): Remove blank assignment when go1.3.3 is no longer supported. + for range nodes { // Scan the array cells D[0], D[1], ... until // finding an i for which D[i] is nonempty. var ( @@ -115,20 +115,20 @@ func BronKerbosch(g graph.Undirected) [][]graph.Node { // The algorithm used here is essentially BronKerbosch3 as described at // http://en.wikipedia.org/w/index.php?title=Bron%E2%80%93Kerbosch_algorithm&oldid=656805858 - p := make(internal.Set, len(nodes)) + p := make(set.Nodes, len(nodes)) for _, n := range nodes { p.Add(n) } - x := make(internal.Set) + x := make(set.Nodes) var bk bronKerbosch order, _ := VertexOrdering(g) for _, v := range order { neighbours := g.From(v) - nv := make(internal.Set, len(neighbours)) + nv := make(set.Nodes, len(neighbours)) for _, n := range neighbours { nv.Add(n) } - bk.maximalCliquePivot(g, []graph.Node{v}, make(internal.Set).Intersect(p, nv), make(internal.Set).Intersect(x, nv)) + bk.maximalCliquePivot(g, []graph.Node{v}, make(set.Nodes).Intersect(p, nv), make(set.Nodes).Intersect(x, nv)) p.Remove(v) x.Add(v) } @@ -137,14 +137,14 @@ func BronKerbosch(g graph.Undirected) [][]graph.Node { type bronKerbosch [][]graph.Node -func (bk *bronKerbosch) maximalCliquePivot(g graph.Undirected, r []graph.Node, p, x internal.Set) { +func (bk *bronKerbosch) maximalCliquePivot(g graph.Undirected, r []graph.Node, p, x set.Nodes) { if len(p) == 0 && len(x) == 0 { *bk = append(*bk, r) return } neighbours := bk.choosePivotFrom(g, p, x) - nu := make(internal.Set, len(neighbours)) + nu := make(set.Nodes, len(neighbours)) for _, n := range neighbours { nu.Add(n) } @@ -153,7 +153,7 @@ func (bk *bronKerbosch) maximalCliquePivot(g graph.Undirected, r []graph.Node, p continue } neighbours := g.From(v) - nv := make(internal.Set, len(neighbours)) + nv := make(set.Nodes, len(neighbours)) for _, n := range neighbours { nv.Add(n) } @@ -170,13 +170,13 @@ func (bk *bronKerbosch) maximalCliquePivot(g graph.Undirected, r []graph.Node, p sr = append(r[:len(r):len(r)], v) } - bk.maximalCliquePivot(g, sr, make(internal.Set).Intersect(p, nv), make(internal.Set).Intersect(x, nv)) + bk.maximalCliquePivot(g, sr, make(set.Nodes).Intersect(p, nv), make(set.Nodes).Intersect(x, nv)) p.Remove(v) x.Add(v) } } -func (*bronKerbosch) choosePivotFrom(g graph.Undirected, p, x internal.Set) (neighbors []graph.Node) { +func (*bronKerbosch) choosePivotFrom(g graph.Undirected, p, x set.Nodes) (neighbors []graph.Node) { // TODO(kortschak): Investigate the impact of pivot choice that maximises // |p ⋂ neighbours(u)| as a function of input size. Until then, leave as // compile time option. @@ -194,7 +194,7 @@ func (*bronKerbosch) choosePivotFrom(g graph.Undirected, p, x internal.Set) (nei max = -1 pivot graph.Node ) - maxNeighbors := func(s internal.Set) { + maxNeighbors := func(s set.Nodes) { outer: for _, u := range s { nb := g.From(u) diff --git a/vendor/github.com/gonum/graph/topo/bron_kerbosch_test.go b/vendor/github.com/gonum/graph/topo/bron_kerbosch_test.go index 3d22c3617886..39fb98fef13e 100644 --- a/vendor/github.com/gonum/graph/topo/bron_kerbosch_test.go +++ b/vendor/github.com/gonum/graph/topo/bron_kerbosch_test.go @@ -2,25 +2,25 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package topo_test +package topo import ( + "math" "reflect" "sort" "testing" - "github.com/gonum/graph/concrete" - "github.com/gonum/graph/internal" - "github.com/gonum/graph/topo" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/graph/simple" ) var vOrderTests = []struct { - g []set + g []intset wantCore [][]int wantK int }{ { - g: []set{ + g: []intset{ 0: linksTo(1, 2, 4, 6), 1: linksTo(2, 4, 6), 2: linksTo(3, 6), @@ -51,17 +51,17 @@ var vOrderTests = []struct { func TestVertexOrdering(t *testing.T) { for i, test := range vOrderTests { - g := concrete.NewGraph() + g := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } - order, core := topo.VertexOrdering(g) + order, core := VertexOrdering(g) if len(core)-1 != test.wantK { t.Errorf("unexpected value of k for test %d: got: %d want: %d", i, len(core)-1, test.wantK) } @@ -90,13 +90,13 @@ func TestVertexOrdering(t *testing.T) { } var bronKerboschTests = []struct { - g []set + g []intset want [][]int }{ { // This is the example given in the Bron-Kerbosch article on wikipedia (renumbered). // http://en.wikipedia.org/w/index.php?title=Bron%E2%80%93Kerbosch_algorithm&oldid=656805858 - g: []set{ + g: []intset{ 0: linksTo(1, 4), 1: linksTo(2, 4), 2: linksTo(3), @@ -136,17 +136,17 @@ var bronKerboschTests = []struct { func TestBronKerbosch(t *testing.T) { for i, test := range bronKerboschTests { - g := concrete.NewGraph() + g := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } - cliques := topo.BronKerbosch(g) + cliques := BronKerbosch(g) got := make([][]int, len(cliques)) for j, c := range cliques { ids := make([]int, len(c)) @@ -156,7 +156,7 @@ func TestBronKerbosch(t *testing.T) { sort.Ints(ids) got[j] = ids } - sort.Sort(internal.BySliceValues(got)) + sort.Sort(ordered.BySliceValues(got)) if !reflect.DeepEqual(got, test.want) { t.Errorf("unexpected cliques for test %d:\ngot: %v\nwant:%v", i, got, test.want) } diff --git a/vendor/github.com/gonum/graph/topo/common_test.go b/vendor/github.com/gonum/graph/topo/common_test.go index 69f3b6c359fa..5d21786cdde3 100644 --- a/vendor/github.com/gonum/graph/topo/common_test.go +++ b/vendor/github.com/gonum/graph/topo/common_test.go @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package topo_test +package topo // batageljZaversnikGraph is the example graph from // figure 1 of http://arxiv.org/abs/cs/0310049v1 -var batageljZaversnikGraph = []set{ +var batageljZaversnikGraph = []intset{ 0: nil, 1: linksTo(2, 3), @@ -32,14 +32,14 @@ var batageljZaversnikGraph = []set{ 20: nil, } -// set is an integer set. -type set map[int]struct{} +// intset is an integer set. +type intset map[int]struct{} -func linksTo(i ...int) set { +func linksTo(i ...int) intset { if len(i) == 0 { return nil } - s := make(set) + s := make(intset) for _, v := range i { s[v] = struct{}{} } diff --git a/vendor/github.com/gonum/graph/topo/johnson_cycles.go b/vendor/github.com/gonum/graph/topo/johnson_cycles.go index 36d4cbd0a1d8..b62d110e1fe8 100644 --- a/vendor/github.com/gonum/graph/topo/johnson_cycles.go +++ b/vendor/github.com/gonum/graph/topo/johnson_cycles.go @@ -8,7 +8,8 @@ import ( "sort" "github.com/gonum/graph" - "github.com/gonum/graph/internal" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/graph/internal/set" ) // johnson implements Johnson's "Finding all the elementary @@ -17,8 +18,8 @@ import ( // Comments in the johnson methods are kept in sync with the comments // and labels from the paper. type johnson struct { - adjacent johnsonGraph // SCC adjacency list. - b []internal.IntSet // Johnson's "B-list". + adjacent johnsonGraph // SCC adjacency list. + b []set.Ints // Johnson's "B-list". blocked []bool s int @@ -32,7 +33,7 @@ func CyclesIn(g graph.Directed) [][]graph.Node { jg := johnsonGraphFrom(g) j := johnson{ adjacent: jg, - b: make([]internal.IntSet, len(jg.orig)), + b: make([]set.Ints, len(jg.orig)), blocked: make([]bool, len(jg.orig)), } @@ -57,7 +58,7 @@ func CyclesIn(g graph.Directed) [][]graph.Node { } if len(j.adjacent.succ[v.ID()]) > 0 { j.blocked[i] = false - j.b[i] = make(internal.IntSet) + j.b[i] = make(set.Ints) } } //L3: @@ -125,26 +126,26 @@ type johnsonGraph struct { orig []graph.Node index map[int]int - nodes internal.IntSet - succ map[int]internal.IntSet + nodes set.Ints + succ map[int]set.Ints } // johnsonGraphFrom returns a deep copy of the graph g. func johnsonGraphFrom(g graph.Directed) johnsonGraph { nodes := g.Nodes() - sort.Sort(byID(nodes)) + sort.Sort(ordered.ByID(nodes)) c := johnsonGraph{ orig: nodes, index: make(map[int]int, len(nodes)), - nodes: make(internal.IntSet, len(nodes)), - succ: make(map[int]internal.IntSet), + nodes: make(set.Ints, len(nodes)), + succ: make(map[int]set.Ints), } for i, u := range nodes { c.index[u.ID()] = i for _, v := range g.From(u) { if c.succ[u.ID()] == nil { - c.succ[u.ID()] = make(internal.IntSet) + c.succ[u.ID()] = make(set.Ints) c.nodes.Add(u.ID()) } c.nodes.Add(v.ID()) @@ -154,12 +155,6 @@ func johnsonGraphFrom(g graph.Directed) johnsonGraph { return c } -type byID []graph.Node - -func (n byID) Len() int { return len(n) } -func (n byID) Less(i, j int) bool { return n[i].ID() < n[j].ID() } -func (n byID) Swap(i, j int) { n[i], n[j] = n[j], n[i] } - // order returns the order of the graph. func (g johnsonGraph) order() int { return g.nodes.Count() } @@ -210,8 +205,8 @@ func (g johnsonGraph) sccSubGraph(sccs [][]graph.Node, min int) johnsonGraph { sub := johnsonGraph{ orig: g.orig, index: g.index, - nodes: make(internal.IntSet), - succ: make(map[int]internal.IntSet), + nodes: make(set.Ints), + succ: make(map[int]set.Ints), } var n int @@ -224,7 +219,7 @@ func (g johnsonGraph) sccSubGraph(sccs [][]graph.Node, min int) johnsonGraph { for _, v := range scc { if _, ok := g.succ[u.ID()][v.ID()]; ok { if sub.succ[u.ID()] == nil { - sub.succ[u.ID()] = make(internal.IntSet) + sub.succ[u.ID()] = make(set.Ints) sub.nodes.Add(u.ID()) } sub.nodes.Add(v.ID()) @@ -265,19 +260,19 @@ func (g johnsonGraph) From(n graph.Node) []graph.Node { } func (johnsonGraph) Has(graph.Node) bool { - panic("search: unintended use of johnsonGraph") + panic("topo: unintended use of johnsonGraph") } -func (johnsonGraph) HasEdge(_, _ graph.Node) bool { - panic("search: unintended use of johnsonGraph") +func (johnsonGraph) HasEdgeBetween(_, _ graph.Node) bool { + panic("topo: unintended use of johnsonGraph") } func (johnsonGraph) Edge(_, _ graph.Node) graph.Edge { - panic("search: unintended use of johnsonGraph") + panic("topo: unintended use of johnsonGraph") } func (johnsonGraph) HasEdgeFromTo(_, _ graph.Node) bool { - panic("search: unintended use of johnsonGraph") + panic("topo: unintended use of johnsonGraph") } func (johnsonGraph) To(graph.Node) []graph.Node { - panic("search: unintended use of johnsonGraph") + panic("topo: unintended use of johnsonGraph") } type johnsonGraphNode int diff --git a/vendor/github.com/gonum/graph/topo/johnson_cycles_test.go b/vendor/github.com/gonum/graph/topo/johnson_cycles_test.go index a1ee6e1bfd9b..4c686b35959d 100644 --- a/vendor/github.com/gonum/graph/topo/johnson_cycles_test.go +++ b/vendor/github.com/gonum/graph/topo/johnson_cycles_test.go @@ -2,25 +2,25 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package topo_test +package topo import ( + "math" "reflect" "sort" "testing" - "github.com/gonum/graph/concrete" - "github.com/gonum/graph/internal" - "github.com/gonum/graph/topo" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/graph/simple" ) var cyclesInTests = []struct { - g []set + g []intset sccs [][]int want [][]int }{ { - g: []set{ + g: []intset{ 0: linksTo(1), 1: linksTo(2, 7), 2: linksTo(3, 6), @@ -36,7 +36,7 @@ var cyclesInTests = []struct { }, }, { - g: []set{ + g: []intset{ 0: linksTo(1, 2, 3), 1: linksTo(2), 2: linksTo(3), @@ -47,7 +47,7 @@ var cyclesInTests = []struct { }, }, { - g: []set{ + g: []intset{ 0: linksTo(1), 1: linksTo(0, 2), 2: linksTo(1), @@ -58,7 +58,7 @@ var cyclesInTests = []struct { }, }, { - g: []set{ + g: []intset{ 0: linksTo(1), 1: linksTo(2, 3), 2: linksTo(4, 5), @@ -70,7 +70,7 @@ var cyclesInTests = []struct { want: nil, }, { - g: []set{ + g: []intset{ 0: linksTo(1), 1: linksTo(2, 3, 4), 2: linksTo(0, 3), @@ -86,18 +86,18 @@ var cyclesInTests = []struct { func TestCyclesIn(t *testing.T) { for i, test := range cyclesInTests { - g := concrete.NewDirectedGraph() - g.AddNode(concrete.Node(-10)) // Make sure we test graphs with sparse IDs. + g := simple.NewDirectedGraph(0, math.Inf(1)) + g.AddNode(simple.Node(-10)) // Make sure we test graphs with sparse IDs. for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } - cycles := topo.CyclesIn(g) + cycles := CyclesIn(g) var got [][]int if cycles != nil { got = make([][]int, len(cycles)) @@ -111,7 +111,7 @@ func TestCyclesIn(t *testing.T) { } got[j] = ids } - sort.Sort(internal.BySliceValues(got)) + sort.Sort(ordered.BySliceValues(got)) if !reflect.DeepEqual(got, test.want) { t.Errorf("unexpected johnson result for %d:\n\tgot:%#v\n\twant:%#v", i, got, test.want) } diff --git a/vendor/github.com/gonum/graph/topo/tarjan.go b/vendor/github.com/gonum/graph/topo/tarjan.go index 908358cd4940..0c7d9681347c 100644 --- a/vendor/github.com/gonum/graph/topo/tarjan.go +++ b/vendor/github.com/gonum/graph/topo/tarjan.go @@ -8,8 +8,10 @@ import ( "fmt" "sort" + "golang.org/x/tools/container/intsets" + "github.com/gonum/graph" - "github.com/gonum/graph/internal" + "github.com/gonum/graph/internal/ordered" ) // Unorderable is an error containing sets of unorderable graph.Nodes. @@ -29,6 +31,8 @@ func (e Unorderable) Error() string { return fmt.Sprintf("topo: no topological ordering: cyclic components: %v", [][]graph.Node(e)) } +func lexical(nodes []graph.Node) { sort.Sort(ordered.ByID(nodes)) } + // Sort performs a topological sort of the directed graph g returning the 'from' to 'to' // sort order. If a topological ordering is not possible, an Unorderable error is returned // listing cyclic components in g with each cyclic component's members sorted by ID. When @@ -36,17 +40,37 @@ func (e Unorderable) Error() string { // the sorted nodes is marked with a nil graph.Node. func Sort(g graph.Directed) (sorted []graph.Node, err error) { sccs := TarjanSCC(g) - sorted = make([]graph.Node, 0, len(sccs)) + return sortedFrom(sccs, lexical) +} + +// SortStabilized performs a topological sort of the directed graph g returning the 'from' +// to 'to' sort order, or the order defined by the in place order sort function where there +// is no unambiguous topological ordering. If a topological ordering is not possible, an +// Unorderable error is returned listing cyclic components in g with each cyclic component's +// members sorted by the provided order function. If order is nil, nodes are ordered lexically +// by node ID. When an Unorderable error is returned, each cyclic component's topological +// position within the sorted nodes is marked with a nil graph.Node. +func SortStabilized(g graph.Directed, order func([]graph.Node)) (sorted []graph.Node, err error) { + if order == nil { + order = lexical + } + sccs := tarjanSCCstabilized(g, order) + return sortedFrom(sccs, order) +} + +func sortedFrom(sccs [][]graph.Node, order func([]graph.Node)) ([]graph.Node, error) { + sorted := make([]graph.Node, 0, len(sccs)) var sc Unorderable for _, s := range sccs { if len(s) != 1 { - sort.Sort(byID(s)) + order(s) sc = append(sc, s) sorted = append(sorted, nil) continue } sorted = append(sorted, s[0]) } + var err error if sc != nil { for i, j := 0, len(sc)-1; i < j; i, j = i+1, j-1 { sc[i], sc[j] = sc[j], sc[i] @@ -73,13 +97,32 @@ func reverse(p []graph.Node) { // only a little extra testing.) // func TarjanSCC(g graph.Directed) [][]graph.Node { + return tarjanSCCstabilized(g, nil) +} + +func tarjanSCCstabilized(g graph.Directed, order func([]graph.Node)) [][]graph.Node { nodes := g.Nodes() + var succ func(graph.Node) []graph.Node + if order == nil { + succ = g.From + } else { + order(nodes) + reverse(nodes) + + succ = func(n graph.Node) []graph.Node { + to := g.From(n) + order(to) + reverse(to) + return to + } + } + t := tarjan{ - succ: g.From, + succ: succ, indexTable: make(map[int]int, len(nodes)), lowLink: make(map[int]int, len(nodes)), - onStack: make(internal.IntSet, len(nodes)), + onStack: &intsets.Sparse{}, } for _, v := range nodes { if t.indexTable[v.ID()] == 0 { @@ -100,7 +143,7 @@ type tarjan struct { index int indexTable map[int]int lowLink map[int]int - onStack internal.IntSet + onStack *intsets.Sparse stack []graph.Node @@ -117,7 +160,7 @@ func (t *tarjan) strongconnect(v graph.Node) { t.indexTable[vID] = t.index t.lowLink[vID] = t.index t.stack = append(t.stack, v) - t.onStack.Add(vID) + t.onStack.Insert(vID) // Consider successors of v. for _, w := range t.succ(v) { diff --git a/vendor/github.com/gonum/graph/topo/tarjan_test.go b/vendor/github.com/gonum/graph/topo/tarjan_test.go index 504633fc2f12..3c854a439c7b 100644 --- a/vendor/github.com/gonum/graph/topo/tarjan_test.go +++ b/vendor/github.com/gonum/graph/topo/tarjan_test.go @@ -2,22 +2,23 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package topo_test +package topo import ( + "math" "reflect" "sort" "testing" - "github.com/gonum/graph/concrete" - "github.com/gonum/graph/internal" - "github.com/gonum/graph/topo" + "github.com/gonum/graph" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/graph/simple" ) type interval struct{ start, end int } var tarjanTests = []struct { - g []set + g []intset ambiguousOrder []interval want [][]int @@ -27,7 +28,7 @@ var tarjanTests = []struct { sortable bool }{ { - g: []set{ + g: []intset{ 0: linksTo(1), 1: linksTo(2, 7), 2: linksTo(3, 6), @@ -48,7 +49,7 @@ var tarjanTests = []struct { sortable: false, }, { - g: []set{ + g: []intset{ 0: linksTo(1, 2, 3), 1: linksTo(2), 2: linksTo(3), @@ -65,7 +66,7 @@ var tarjanTests = []struct { sortable: false, }, { - g: []set{ + g: []intset{ 0: linksTo(1), 1: linksTo(0, 2), 2: linksTo(1), @@ -80,7 +81,7 @@ var tarjanTests = []struct { sortable: false, }, { - g: []set{ + g: []intset{ 0: linksTo(1), 1: linksTo(2, 3), 2: linksTo(4, 5), @@ -104,7 +105,7 @@ var tarjanTests = []struct { sortable: true, }, { - g: []set{ + g: []intset{ 0: linksTo(1), 1: linksTo(2, 3, 4), 2: linksTo(0, 3), @@ -129,17 +130,17 @@ var tarjanTests = []struct { func TestSort(t *testing.T) { for i, test := range tarjanTests { - g := concrete.NewDirectedGraph() + g := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } - sorted, err := topo.Sort(g) + sorted, err := Sort(g) var gotSortedLen int for _, n := range sorted { if n != nil { @@ -152,25 +153,25 @@ func TestSort(t *testing.T) { if err == nil != test.sortable { t.Errorf("unexpected sortability for test %d: got error: %v want: nil-error=%t", i, err, test.sortable) } - if err != nil && len(err.(topo.Unorderable)) != test.unorderableLength { - t.Errorf("unexpected number of unorderable nodes for test %d: got:%d want:%d", i, len(err.(topo.Unorderable)), test.unorderableLength) + if err != nil && len(err.(Unorderable)) != test.unorderableLength { + t.Errorf("unexpected number of unorderable nodes for test %d: got:%d want:%d", i, len(err.(Unorderable)), test.unorderableLength) } } } func TestTarjanSCC(t *testing.T) { for i, test := range tarjanTests { - g := concrete.NewDirectedGraph() + g := simple.NewDirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } - gotSCCs := topo.TarjanSCC(g) + gotSCCs := TarjanSCC(g) // tarjan.strongconnect does range iteration over maps, // so sort SCC members to ensure consistent ordering. gotIDs := make([][]int, len(gotSCCs)) @@ -182,11 +183,127 @@ func TestTarjanSCC(t *testing.T) { sort.Ints(gotIDs[i]) } for _, iv := range test.ambiguousOrder { - sort.Sort(internal.BySliceValues(test.want[iv.start:iv.end])) - sort.Sort(internal.BySliceValues(gotIDs[iv.start:iv.end])) + sort.Sort(ordered.BySliceValues(test.want[iv.start:iv.end])) + sort.Sort(ordered.BySliceValues(gotIDs[iv.start:iv.end])) } if !reflect.DeepEqual(gotIDs, test.want) { t.Errorf("unexpected Tarjan scc result for %d:\n\tgot:%v\n\twant:%v", i, gotIDs, test.want) } } } + +var stabilizedSortTests = []struct { + g []intset + + want []graph.Node + err error +}{ + { + g: []intset{ + 0: linksTo(1), + 1: linksTo(2, 7), + 2: linksTo(3, 6), + 3: linksTo(4), + 4: linksTo(2, 5), + 6: linksTo(3, 5), + 7: linksTo(0, 6), + }, + + want: []graph.Node{nil, nil, simple.Node(5)}, + err: Unorderable{ + {simple.Node(0), simple.Node(1), simple.Node(7)}, + {simple.Node(2), simple.Node(3), simple.Node(4), simple.Node(6)}, + }, + }, + { + g: []intset{ + 0: linksTo(1, 2, 3), + 1: linksTo(2), + 2: linksTo(3), + 3: linksTo(1), + }, + + want: []graph.Node{simple.Node(0), nil}, + err: Unorderable{ + {simple.Node(1), simple.Node(2), simple.Node(3)}, + }, + }, + { + g: []intset{ + 0: linksTo(1), + 1: linksTo(0, 2), + 2: linksTo(1), + }, + + want: []graph.Node{nil}, + err: Unorderable{ + {simple.Node(0), simple.Node(1), simple.Node(2)}, + }, + }, + { + g: []intset{ + 0: linksTo(1), + 1: linksTo(2, 3), + 2: linksTo(4, 5), + 3: linksTo(4, 5), + 4: linksTo(6), + 5: nil, + 6: nil, + }, + + want: []graph.Node{simple.Node(0), simple.Node(1), simple.Node(2), simple.Node(3), simple.Node(4), simple.Node(5), simple.Node(6)}, + err: nil, + }, + { + g: []intset{ + 0: linksTo(1), + 1: linksTo(2, 3, 4), + 2: linksTo(0, 3), + 3: linksTo(4), + 4: linksTo(3), + }, + + want: []graph.Node{nil, nil}, + err: Unorderable{ + {simple.Node(0), simple.Node(1), simple.Node(2)}, + {simple.Node(3), simple.Node(4)}, + }, + }, + { + g: []intset{ + 0: linksTo(1, 2, 3, 4, 5, 6), + 1: linksTo(7), + 2: linksTo(7), + 3: linksTo(7), + 4: linksTo(7), + 5: linksTo(7), + 6: linksTo(7), + 7: nil, + }, + + want: []graph.Node{simple.Node(0), simple.Node(1), simple.Node(2), simple.Node(3), simple.Node(4), simple.Node(5), simple.Node(6), simple.Node(7)}, + err: nil, + }, +} + +func TestSortStabilized(t *testing.T) { + for i, test := range stabilizedSortTests { + g := simple.NewDirectedGraph(0, math.Inf(1)) + for u, e := range test.g { + // Add nodes that are not defined by an edge. + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) + } + for v := range e { + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) + } + } + got, err := SortStabilized(g, nil) + if !reflect.DeepEqual(got, test.want) { + t.Errorf("unexpected sort result for test %d: got:%d want:%d", i, got, test.want) + } + if !reflect.DeepEqual(err, test.err) { + t.Errorf("unexpected sort error for test %d: got:%v want:%v", i, err, test.want) + } + } +} diff --git a/vendor/github.com/gonum/graph/topo/topo.go b/vendor/github.com/gonum/graph/topo/topo.go index f4c3a2a1f835..23568bb7b5a2 100644 --- a/vendor/github.com/gonum/graph/topo/topo.go +++ b/vendor/github.com/gonum/graph/topo/topo.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Package topo provides graph topology analysis functions. package topo import ( @@ -25,7 +26,7 @@ func IsPathIn(g graph.Graph, path []graph.Node) bool { case graph.Directed: canReach = g.HasEdgeFromTo default: - canReach = g.HasEdge + canReach = g.HasEdgeBetween } for i, u := range path[:len(path)-1] { @@ -37,6 +38,16 @@ func IsPathIn(g graph.Graph, path []graph.Node) bool { } } +// PathExistsIn returns whether there is a path in g starting at from extending +// to to. +// +// PathExistsIn exists as a helper function. If many tests for path existence +// are being performed, other approaches will be more efficient. +func PathExistsIn(g graph.Graph, from, to graph.Node) bool { + var t traverse.BreadthFirst + return t.Walk(g, from, func(n graph.Node, _ int) bool { return n.ID() == to.ID() }) != nil +} + // ConnectedComponents returns the connected components of the undirected graph g. func ConnectedComponents(g graph.Undirected) [][]graph.Node { var ( diff --git a/vendor/github.com/gonum/graph/topo/topo_test.go b/vendor/github.com/gonum/graph/topo/topo_test.go index d903ee164d99..1cd086a49a5b 100644 --- a/vendor/github.com/gonum/graph/topo/topo_test.go +++ b/vendor/github.com/gonum/graph/topo/topo_test.go @@ -2,60 +2,135 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package topo_test +package topo import ( + "math" "reflect" "sort" "testing" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" - "github.com/gonum/graph/internal" - "github.com/gonum/graph/topo" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/graph/simple" ) func TestIsPath(t *testing.T) { - dg := concrete.NewDirectedGraph() - if !topo.IsPathIn(dg, nil) { + dg := simple.NewDirectedGraph(0, math.Inf(1)) + if !IsPathIn(dg, nil) { t.Error("IsPath returns false on nil path") } - p := []graph.Node{concrete.Node(0)} - if topo.IsPathIn(dg, p) { + p := []graph.Node{simple.Node(0)} + if IsPathIn(dg, p) { t.Error("IsPath returns true on nonexistant node") } dg.AddNode(p[0]) - if !topo.IsPathIn(dg, p) { + if !IsPathIn(dg, p) { t.Error("IsPath returns false on single-length path with existing node") } - p = append(p, concrete.Node(1)) + p = append(p, simple.Node(1)) dg.AddNode(p[1]) - if topo.IsPathIn(dg, p) { + if IsPathIn(dg, p) { t.Error("IsPath returns true on bad path of length 2") } - dg.SetEdge(concrete.Edge{p[0], p[1]}, 1) - if !topo.IsPathIn(dg, p) { + dg.SetEdge(simple.Edge{F: p[0], T: p[1], W: 1}) + if !IsPathIn(dg, p) { t.Error("IsPath returns false on correct path of length 2") } p[0], p[1] = p[1], p[0] - if topo.IsPathIn(dg, p) { + if IsPathIn(dg, p) { t.Error("IsPath erroneously returns true for a reverse path") } - p = []graph.Node{p[1], p[0], concrete.Node(2)} - dg.SetEdge(concrete.Edge{p[1], p[2]}, 1) - if !topo.IsPathIn(dg, p) { + p = []graph.Node{p[1], p[0], simple.Node(2)} + dg.SetEdge(simple.Edge{F: p[1], T: p[2], W: 1}) + if !IsPathIn(dg, p) { t.Error("IsPath does not find a correct path for path > 2 nodes") } - ug := concrete.NewGraph() - ug.SetEdge(concrete.Edge{p[1], p[0]}, 1) - ug.SetEdge(concrete.Edge{p[1], p[2]}, 1) - if !topo.IsPathIn(dg, p) { + ug := simple.NewUndirectedGraph(0, math.Inf(1)) + ug.SetEdge(simple.Edge{F: p[1], T: p[0], W: 1}) + ug.SetEdge(simple.Edge{F: p[1], T: p[2], W: 1}) + if !IsPathIn(dg, p) { t.Error("IsPath does not correctly account for undirected behavior") } } +var pathExistsInUndirectedTests = []struct { + g []intset + from, to int + want bool +}{ + {g: batageljZaversnikGraph, from: 0, to: 0, want: true}, + {g: batageljZaversnikGraph, from: 0, to: 1, want: false}, + {g: batageljZaversnikGraph, from: 1, to: 2, want: true}, + {g: batageljZaversnikGraph, from: 2, to: 1, want: true}, + {g: batageljZaversnikGraph, from: 2, to: 12, want: false}, + {g: batageljZaversnikGraph, from: 20, to: 6, want: true}, +} + +func TestPathExistsInUndirected(t *testing.T) { + for i, test := range pathExistsInUndirectedTests { + g := simple.NewUndirectedGraph(0, math.Inf(1)) + + for u, e := range test.g { + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) + } + for v := range e { + if !g.Has(simple.Node(v)) { + g.AddNode(simple.Node(v)) + } + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) + } + } + + got := PathExistsIn(g, simple.Node(test.from), simple.Node(test.to)) + if got != test.want { + t.Errorf("unexpected result for path existance in test %d: got:%t want %t", i, got, test.want) + } + } +} + +var pathExistsInDirectedTests = []struct { + g []intset + from, to int + want bool +}{ + // The graph definition is such that from node IDs are + // less than to node IDs. + {g: batageljZaversnikGraph, from: 0, to: 0, want: true}, + {g: batageljZaversnikGraph, from: 0, to: 1, want: false}, + {g: batageljZaversnikGraph, from: 1, to: 2, want: true}, + {g: batageljZaversnikGraph, from: 2, to: 1, want: false}, + {g: batageljZaversnikGraph, from: 2, to: 12, want: false}, + {g: batageljZaversnikGraph, from: 20, to: 6, want: false}, + {g: batageljZaversnikGraph, from: 6, to: 20, want: true}, +} + +func TestPathExistsInDirected(t *testing.T) { + for i, test := range pathExistsInDirectedTests { + g := simple.NewDirectedGraph(0, math.Inf(1)) + + for u, e := range test.g { + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) + } + for v := range e { + if !g.Has(simple.Node(v)) { + g.AddNode(simple.Node(v)) + } + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) + } + } + + got := PathExistsIn(g, simple.Node(test.from), simple.Node(test.to)) + if got != test.want { + t.Errorf("unexpected result for path existance in test %d: got:%t want %t", i, got, test.want) + } + } +} + var connectedComponentTests = []struct { - g []set + g []intset want [][]int }{ { @@ -70,20 +145,20 @@ var connectedComponentTests = []struct { func TestConnectedComponents(t *testing.T) { for i, test := range connectedComponentTests { - g := concrete.NewGraph() + g := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range test.g { - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - if !g.Has(concrete.Node(v)) { - g.AddNode(concrete.Node(v)) + if !g.Has(simple.Node(v)) { + g.AddNode(simple.Node(v)) } - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } - cc := topo.ConnectedComponents(g) + cc := ConnectedComponents(g) got := make([][]int, len(cc)) for j, c := range cc { ids := make([]int, len(c)) @@ -93,7 +168,7 @@ func TestConnectedComponents(t *testing.T) { sort.Ints(ids) got[j] = ids } - sort.Sort(internal.BySliceValues(got)) + sort.Sort(ordered.BySliceValues(got)) if !reflect.DeepEqual(got, test.want) { t.Errorf("unexpected connected components for test %d %T:\ngot: %v\nwant:%v", i, g, got, test.want) } diff --git a/vendor/github.com/gonum/graph/traverse/traverse.go b/vendor/github.com/gonum/graph/traverse/traverse.go index bb0fdad1bcda..6a351b07149e 100644 --- a/vendor/github.com/gonum/graph/traverse/traverse.go +++ b/vendor/github.com/gonum/graph/traverse/traverse.go @@ -6,16 +6,18 @@ package traverse import ( + "golang.org/x/tools/container/intsets" + "github.com/gonum/graph" - "github.com/gonum/graph/internal" + "github.com/gonum/graph/internal/linear" ) // BreadthFirst implements stateful breadth-first graph traversal. type BreadthFirst struct { EdgeFilter func(graph.Edge) bool Visit func(u, v graph.Node) - queue internal.NodeQueue - visited internal.IntSet + queue linear.NodeQueue + visited *intsets.Sparse } // Walk performs a breadth-first traversal of the graph g starting from the given node, @@ -25,10 +27,10 @@ type BreadthFirst struct { // non-nil, it is called with the nodes joined by each followed edge. func (b *BreadthFirst) Walk(g graph.Graph, from graph.Node, until func(n graph.Node, d int) bool) graph.Node { if b.visited == nil { - b.visited = make(internal.IntSet) + b.visited = &intsets.Sparse{} } b.queue.Enqueue(from) - b.visited.Add(from.ID()) + b.visited.Insert(from.ID()) var ( depth int @@ -50,7 +52,7 @@ func (b *BreadthFirst) Walk(g graph.Graph, from graph.Node, until func(n graph.N if b.Visit != nil { b.Visit(t, n) } - b.visited.Add(n.ID()) + b.visited.Insert(n.ID()) children++ b.queue.Enqueue(n) } @@ -91,22 +93,23 @@ func (b *BreadthFirst) WalkAll(g graph.Undirected, before, after func(), during // Visited returned whether the node n was visited during a traverse. func (b *BreadthFirst) Visited(n graph.Node) bool { - _, ok := b.visited[n.ID()] - return ok + return b.visited != nil && b.visited.Has(n.ID()) } // Reset resets the state of the traverser for reuse. func (b *BreadthFirst) Reset() { b.queue.Reset() - b.visited = nil + if b.visited != nil { + b.visited.Clear() + } } // DepthFirst implements stateful depth-first graph traversal. type DepthFirst struct { EdgeFilter func(graph.Edge) bool Visit func(u, v graph.Node) - stack internal.NodeStack - visited internal.IntSet + stack linear.NodeStack + visited *intsets.Sparse } // Walk performs a depth-first traversal of the graph g starting from the given node, @@ -116,10 +119,10 @@ type DepthFirst struct { // is called with the nodes joined by each followed edge. func (d *DepthFirst) Walk(g graph.Graph, from graph.Node, until func(graph.Node) bool) graph.Node { if d.visited == nil { - d.visited = make(internal.IntSet) + d.visited = &intsets.Sparse{} } d.stack.Push(from) - d.visited.Add(from.ID()) + d.visited.Insert(from.ID()) for d.stack.Len() > 0 { t := d.stack.Pop() @@ -136,7 +139,7 @@ func (d *DepthFirst) Walk(g graph.Graph, from graph.Node, until func(graph.Node) if d.Visit != nil { d.Visit(t, n) } - d.visited.Add(n.ID()) + d.visited.Insert(n.ID()) d.stack.Push(n) } } @@ -171,12 +174,13 @@ func (d *DepthFirst) WalkAll(g graph.Undirected, before, after func(), during fu // Visited returned whether the node n was visited during a traverse. func (d *DepthFirst) Visited(n graph.Node) bool { - _, ok := d.visited[n.ID()] - return ok + return d.visited != nil && d.visited.Has(n.ID()) } // Reset resets the state of the traverser for reuse. func (d *DepthFirst) Reset() { d.stack = d.stack[:0] - d.visited = nil + if d.visited != nil { + d.visited.Clear() + } } diff --git a/vendor/github.com/gonum/graph/traverse/traverse_test.go b/vendor/github.com/gonum/graph/traverse/traverse_test.go index c5a4e2a449e6..8c33d10eb033 100644 --- a/vendor/github.com/gonum/graph/traverse/traverse_test.go +++ b/vendor/github.com/gonum/graph/traverse/traverse_test.go @@ -2,18 +2,19 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package traverse_test +package traverse import ( "fmt" + "math" "reflect" "sort" "testing" "github.com/gonum/graph" - "github.com/gonum/graph/concrete" - "github.com/gonum/graph/internal" - "github.com/gonum/graph/traverse" + "github.com/gonum/graph/graphs/gen" + "github.com/gonum/graph/internal/ordered" + "github.com/gonum/graph/simple" ) var ( @@ -67,7 +68,7 @@ var breadthFirstTests = []struct { }{ { g: wpBronKerboschGraph, - from: concrete.Node(1), + from: simple.Node(1), final: map[graph.Node]bool{nil: true}, want: [][]int{ {1}, @@ -82,7 +83,7 @@ var breadthFirstTests = []struct { // Do not traverse an edge between 3 and 5. return (e.From().ID() != 3 || e.To().ID() != 5) && (e.From().ID() != 5 || e.To().ID() != 3) }, - from: concrete.Node(1), + from: simple.Node(1), final: map[graph.Node]bool{nil: true}, want: [][]int{ {1}, @@ -92,9 +93,9 @@ var breadthFirstTests = []struct { }, { g: wpBronKerboschGraph, - from: concrete.Node(1), - until: func(n graph.Node, _ int) bool { return n == concrete.Node(3) }, - final: map[graph.Node]bool{concrete.Node(3): true}, + from: simple.Node(1), + until: func(n graph.Node, _ int) bool { return n == simple.Node(3) }, + final: map[graph.Node]bool{simple.Node(3): true}, want: [][]int{ {1}, {0, 2, 4}, @@ -102,7 +103,7 @@ var breadthFirstTests = []struct { }, { g: batageljZaversnikGraph, - from: concrete.Node(13), + from: simple.Node(13), final: map[graph.Node]bool{nil: true}, want: [][]int{ {13}, @@ -114,14 +115,14 @@ var breadthFirstTests = []struct { }, { g: batageljZaversnikGraph, - from: concrete.Node(13), + from: simple.Node(13), until: func(_ graph.Node, d int) bool { return d > 2 }, final: map[graph.Node]bool{ - concrete.Node(11): true, - concrete.Node(12): true, - concrete.Node(18): true, - concrete.Node(19): true, - concrete.Node(20): true, + simple.Node(11): true, + simple.Node(12): true, + simple.Node(18): true, + simple.Node(19): true, + simple.Node(20): true, }, want: [][]int{ {13}, @@ -133,17 +134,17 @@ var breadthFirstTests = []struct { func TestBreadthFirst(t *testing.T) { for i, test := range breadthFirstTests { - g := concrete.NewGraph() + g := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } - w := traverse.BreadthFirst{ + w := BreadthFirst{ EdgeFilter: test.edge, } var got [][]int @@ -179,7 +180,7 @@ var depthFirstTests = []struct { }{ { g: wpBronKerboschGraph, - from: concrete.Node(1), + from: simple.Node(1), final: map[graph.Node]bool{nil: true}, want: []int{0, 1, 2, 3, 4, 5}, }, @@ -189,31 +190,31 @@ var depthFirstTests = []struct { // Do not traverse an edge between 3 and 5. return (e.From().ID() != 3 || e.To().ID() != 5) && (e.From().ID() != 5 || e.To().ID() != 3) }, - from: concrete.Node(1), + from: simple.Node(1), final: map[graph.Node]bool{nil: true}, want: []int{0, 1, 2, 3, 4}, }, { g: wpBronKerboschGraph, - from: concrete.Node(1), - until: func(n graph.Node) bool { return n == concrete.Node(3) }, - final: map[graph.Node]bool{concrete.Node(3): true}, + from: simple.Node(1), + until: func(n graph.Node) bool { return n == simple.Node(3) }, + final: map[graph.Node]bool{simple.Node(3): true}, }, { g: batageljZaversnikGraph, - from: concrete.Node(0), + from: simple.Node(0), final: map[graph.Node]bool{nil: true}, want: []int{0}, }, { g: batageljZaversnikGraph, - from: concrete.Node(3), + from: simple.Node(3), final: map[graph.Node]bool{nil: true}, want: []int{1, 2, 3, 4, 5}, }, { g: batageljZaversnikGraph, - from: concrete.Node(13), + from: simple.Node(13), final: map[graph.Node]bool{nil: true}, want: []int{6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, }, @@ -221,17 +222,17 @@ var depthFirstTests = []struct { func TestDepthFirst(t *testing.T) { for i, test := range depthFirstTests { - g := concrete.NewGraph() + g := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range test.g { // Add nodes that are not defined by an edge. - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } - w := traverse.DepthFirst{ + w := DepthFirst{ EdgeFilter: test.edge, } var got []int @@ -282,34 +283,34 @@ var walkAllTests = []struct { func TestWalkAll(t *testing.T) { for i, test := range walkAllTests { - g := concrete.NewGraph() + g := simple.NewUndirectedGraph(0, math.Inf(1)) for u, e := range test.g { - if !g.Has(concrete.Node(u)) { - g.AddNode(concrete.Node(u)) + if !g.Has(simple.Node(u)) { + g.AddNode(simple.Node(u)) } for v := range e { - if !g.Has(concrete.Node(v)) { - g.AddNode(concrete.Node(v)) + if !g.Has(simple.Node(v)) { + g.AddNode(simple.Node(v)) } - g.SetEdge(concrete.Edge{F: concrete.Node(u), T: concrete.Node(v)}, 0) + g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) } } type walker interface { WalkAll(g graph.Undirected, before, after func(), during func(graph.Node)) } for _, w := range []walker{ - &traverse.BreadthFirst{}, - &traverse.DepthFirst{}, + &BreadthFirst{}, + &DepthFirst{}, } { var ( c []graph.Node cc [][]graph.Node ) switch w := w.(type) { - case *traverse.BreadthFirst: + case *BreadthFirst: w.EdgeFilter = test.edge - case *traverse.DepthFirst: + case *DepthFirst: w.EdgeFilter = test.edge default: panic(fmt.Sprintf("bad walker type: %T", w)) @@ -333,7 +334,7 @@ func TestWalkAll(t *testing.T) { sort.Ints(ids) got[j] = ids } - sort.Sort(internal.BySliceValues(got)) + sort.Sort(ordered.BySliceValues(got)) if !reflect.DeepEqual(got, test.want) { t.Errorf("unexpected connected components for test %d using %T:\ngot: %v\nwant:%v", i, w, got, test.want) } @@ -354,3 +355,80 @@ func linksTo(i ...int) set { } return s } + +var ( + gnpUndirected_10_tenth = gnpUndirected(10, 0.1) + gnpUndirected_100_tenth = gnpUndirected(100, 0.1) + gnpUndirected_1000_tenth = gnpUndirected(1000, 0.1) + gnpUndirected_10_half = gnpUndirected(10, 0.5) + gnpUndirected_100_half = gnpUndirected(100, 0.5) + gnpUndirected_1000_half = gnpUndirected(1000, 0.5) +) + +func gnpUndirected(n int, p float64) graph.Undirected { + g := simple.NewUndirectedGraph(0, math.Inf(1)) + gen.Gnp(g, n, p, nil) + return g +} + +func benchmarkWalkAllBreadthFirst(b *testing.B, g graph.Undirected) { + n := len(g.Nodes()) + b.ResetTimer() + var bft BreadthFirst + for i := 0; i < b.N; i++ { + bft.WalkAll(g, nil, nil, nil) + } + if bft.visited.Len() != n { + b.Fatalf("unexpected number of nodes visited: want: %d got %d", n, bft.visited.Len()) + } +} + +func BenchmarkWalkAllBreadthFirstGnp_10_tenth(b *testing.B) { + benchmarkWalkAllBreadthFirst(b, gnpUndirected_10_tenth) +} +func BenchmarkWalkAllBreadthFirstGnp_100_tenth(b *testing.B) { + benchmarkWalkAllBreadthFirst(b, gnpUndirected_100_tenth) +} +func BenchmarkWalkAllBreadthFirstGnp_1000_tenth(b *testing.B) { + benchmarkWalkAllBreadthFirst(b, gnpUndirected_1000_tenth) +} +func BenchmarkWalkAllBreadthFirstGnp_10_half(b *testing.B) { + benchmarkWalkAllBreadthFirst(b, gnpUndirected_10_half) +} +func BenchmarkWalkAllBreadthFirstGnp_100_half(b *testing.B) { + benchmarkWalkAllBreadthFirst(b, gnpUndirected_100_half) +} +func BenchmarkWalkAllBreadthFirstGnp_1000_half(b *testing.B) { + benchmarkWalkAllBreadthFirst(b, gnpUndirected_1000_half) +} + +func benchmarkWalkAllDepthFirst(b *testing.B, g graph.Undirected) { + n := len(g.Nodes()) + b.ResetTimer() + var dft DepthFirst + for i := 0; i < b.N; i++ { + dft.WalkAll(g, nil, nil, nil) + } + if dft.visited.Len() != n { + b.Fatalf("unexpected number of nodes visited: want: %d got %d", n, dft.visited.Len()) + } +} + +func BenchmarkWalkAllDepthFirstGnp_10_tenth(b *testing.B) { + benchmarkWalkAllDepthFirst(b, gnpUndirected_10_tenth) +} +func BenchmarkWalkAllDepthFirstGnp_100_tenth(b *testing.B) { + benchmarkWalkAllDepthFirst(b, gnpUndirected_100_tenth) +} +func BenchmarkWalkAllDepthFirstGnp_1000_tenth(b *testing.B) { + benchmarkWalkAllDepthFirst(b, gnpUndirected_1000_tenth) +} +func BenchmarkWalkAllDepthFirstGnp_10_half(b *testing.B) { + benchmarkWalkAllDepthFirst(b, gnpUndirected_10_half) +} +func BenchmarkWalkAllDepthFirstGnp_100_half(b *testing.B) { + benchmarkWalkAllDepthFirst(b, gnpUndirected_100_half) +} +func BenchmarkWalkAllDepthFirstGnp_1000_half(b *testing.B) { + benchmarkWalkAllDepthFirst(b, gnpUndirected_1000_half) +} diff --git a/vendor/github.com/gonum/graph/undirect.go b/vendor/github.com/gonum/graph/undirect.go new file mode 100644 index 000000000000..3dd3dfb30753 --- /dev/null +++ b/vendor/github.com/gonum/graph/undirect.go @@ -0,0 +1,185 @@ +// 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 + +import ( + "golang.org/x/tools/container/intsets" +) + +// Undirect converts a directed graph to an undirected graph, resolving +// edge weight conflicts. +type Undirect struct { + G Directed + + // Absent is the value used to + // represent absent edge weights + // passed to Merge if the reverse + // edge is present. + Absent float64 + + // Merge defines how discordant edge + // weights in G are resolved. A merge + // is performed if at least one edge + // exists between the nodes being + // considered. The edges corresponding + // to the two weights are also passed, + // in the same order. + // The order of weight parameters + // passed to Merge is not defined, so + // the function should be commutative. + // If Merge is nil, the arithmetic + // mean is used to merge weights. + Merge func(x, y float64, xe, ye Edge) float64 +} + +var ( + _ Undirected = Undirect{} + _ Weighter = Undirect{} +) + +// Has returns whether the node exists within the graph. +func (g Undirect) Has(n Node) bool { return g.G.Has(n) } + +// Nodes returns all the nodes in the graph. +func (g Undirect) Nodes() []Node { return g.G.Nodes() } + +// From returns all nodes in g that can be reached directly from u. +func (g Undirect) From(u Node) []Node { + var ( + nodes []Node + seen intsets.Sparse + ) + for _, n := range g.G.From(u) { + seen.Insert(n.ID()) + nodes = append(nodes, n) + } + for _, n := range g.G.To(u) { + id := n.ID() + if seen.Has(id) { + continue + } + seen.Insert(id) + nodes = append(nodes, n) + } + return nodes +} + +// HasEdgeBetween returns whether an edge exists between nodes x and y. +func (g Undirect) HasEdgeBetween(x, y Node) bool { return g.G.HasEdgeBetween(x, y) } + +// Edge returns the edge from u to v if such an edge exists and nil otherwise. +// The node v must be directly reachable from u as defined by the From method. +// If an edge exists, the Edge returned is an EdgePair. The weight of +// the edge is determined by applying the Merge func to the weights of the +// edges between u and v. +func (g Undirect) Edge(u, v Node) Edge { return g.EdgeBetween(u, v) } + +// EdgeBetween returns the edge between nodes x and y. If an edge exists, the +// Edge returned is an EdgePair. The weight of the edge is determined by +// applying the Merge func to the weights of edges between x and y. +func (g Undirect) EdgeBetween(x, y Node) Edge { + fe := g.G.Edge(x, y) + re := g.G.Edge(y, x) + if fe == nil && re == nil { + return nil + } + + var f, r float64 + if wg, ok := g.G.(Weighter); ok { + f, ok = wg.Weight(x, y) + if !ok { + f = g.Absent + } + r, ok = wg.Weight(y, x) + if !ok { + r = g.Absent + } + } else { + f = g.Absent + if fe != nil { + f = fe.Weight() + } + r = g.Absent + if re != nil { + r = re.Weight() + } + } + + var w float64 + if g.Merge == nil { + w = (f + r) / 2 + } else { + w = g.Merge(f, r, fe, re) + } + return EdgePair{E: [2]Edge{fe, re}, W: w} +} + +// Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge. +// If x and y are the same node the internal node weight is returned. If there is no joining +// edge between the two nodes the weight value returned is zero. Weight returns true if an edge +// exists between x and y or if x and y have the same ID, false otherwise. +func (g Undirect) Weight(x, y Node) (w float64, ok bool) { + fe := g.G.Edge(x, y) + re := g.G.Edge(y, x) + + var f, r float64 + if wg, wOk := g.G.(Weighter); wOk { + var fOk, rOK bool + f, fOk = wg.Weight(x, y) + if !fOk { + f = g.Absent + } + r, rOK = wg.Weight(y, x) + if !rOK { + r = g.Absent + } + ok = fOk || rOK + } else { + f = g.Absent + if fe != nil { + f = fe.Weight() + ok = true + } + r = g.Absent + if re != nil { + r = re.Weight() + ok = true + } + } + + if g.Merge == nil { + return (f + r) / 2, ok + } + return g.Merge(f, r, fe, re), ok +} + +// EdgePair is an opposed pair of directed edges. +type EdgePair struct { + E [2]Edge + W float64 +} + +// From returns the from node of the first non-nil edge, or nil. +func (e EdgePair) From() Node { + if e.E[0] != nil { + return e.E[0].From() + } else if e.E[1] != nil { + return e.E[1].From() + } + return nil +} + +// To returns the to node of the first non-nil edge, or nil. +func (e EdgePair) To() Node { + if e.E[0] != nil { + return e.E[0].To() + } else if e.E[1] != nil { + return e.E[1].To() + } + return nil +} + +// Weight returns the merged edge weights of the two edges. +func (e EdgePair) Weight() float64 { return e.W } diff --git a/vendor/github.com/gonum/graph/undirect_test.go b/vendor/github.com/gonum/graph/undirect_test.go new file mode 100644 index 000000000000..d811e954973e --- /dev/null +++ b/vendor/github.com/gonum/graph/undirect_test.go @@ -0,0 +1,126 @@ +// 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" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" + "github.com/gonum/matrix/mat64" +) + +var directedGraphs = []struct { + g func() graph.DirectedBuilder + edges []simple.Edge + absent float64 + merge func(x, y float64, xe, ye graph.Edge) float64 + + want mat64.Matrix +}{ + { + g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) }, + edges: []simple.Edge{ + {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: mat64.NewSymDense(3, []float64{ + 0, (1. + 2.) / 2., 0, + (1. + 2.) / 2., 0, 1. / 2., + 0, 1. / 2., 0, + }), + }, + { + g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) }, + edges: []simple.Edge{ + {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: mat64.NewSymDense(3, []float64{ + 0, math.Sqrt(1 * 2), 0, + math.Sqrt(1 * 2), 0, math.Sqrt(1 * 1), + 0, math.Sqrt(1 * 1), 0, + }), + }, + { + g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) }, + edges: []simple.Edge{ + {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: mat64.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() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) }, + edges: []simple.Edge{ + {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: mat64.NewSymDense(3, []float64{ + 0, math.Min(1, 2), 0, + math.Min(1, 2), 0, 1, + 0, 1, 0, + }), + }, + { + g: func() graph.DirectedBuilder { return simple.NewDirectedGraph(0, 0) }, + edges: []simple.Edge{ + {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: mat64.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 _, test := range directedGraphs { + g := test.g() + for _, e := range test.edges { + g.SetEdge(e) + } + + src := graph.Undirect{G: g, Absent: test.absent, Merge: test.merge} + dst := simple.NewUndirectedMatrixFrom(src.Nodes(), 0, 0, 0) + for _, u := range src.Nodes() { + for _, v := range src.From(u) { + dst.SetEdge(src.Edge(u, v)) + } + } + + if !mat64.Equal(dst.Matrix(), test.want) { + t.Errorf("unexpected result:\ngot:\n%.4v\nwant:\n%.4v", + mat64.Formatted(dst.Matrix()), + mat64.Formatted(test.want), + ) + } + } +}