Outdated egg!
This is an egg for CHICKEN 4, the unsupported old release. You're almost certainly looking for the CHICKEN 5 version of this egg, if it exists.
If it does not exist, there may be equivalent functionality provided by another egg; have a look at the egg index. Otherwise, please consider porting this egg to the current version of CHICKEN.
graphs
Provides graphs, digraphs, multigraphs, and multidigraphs for CHICKEN 4.9+
Overview
The purpose of this egg is to provide a generic interface for graphs, digraphs, multigraphs and multidigraphs to CHICKEN Scheme. Compared to other combinatorial graphing eggs in the coop, this egg aims to be generic and cover a broad range of use cases. Moreover, unlike many graphing libraries, an interface for multigraphs and multidigraphs are provided.
Installation
$ chicken-install graphs
or
$ chicken-install graphs -test
if you want to run the tests for the egg in addition.
Usage
(use graphs) (use graphs-derived)
The latter module extends the primitives found in the graphs module to perform other graph-related functionality (e.g. searching for isomorphisms between graphs).
Description
The graphs module is implemented primarily as a set of COOPS classes and methods that set up rules for managing vertices and edges within a (multi)(di)graph. The interface provides both destructive (!) and non-destructive operations for different graph primitives; however, all procedures start with graph-, regardless of the type of graph they are. Thanks to COOPS, everything dispatches properly and the rules for (multi)(di)graphs are maintained. This way, whether you're using a multigraph or a graph, you can still (for example) call (graph-vertex-add G 'a) and the vertex a will still be added to (multi)graph G.
NOTE: for methods below, unless otherwise specified by distinguishing between (multi)(di)graph classes, all methods _should_ work without modification for every type of graph provided by this egg.
graphs
Classes
[class] <multidigraph>[class] <multigraph>
[class] <digraph>
[class] <graph>
The four primary classes that are provided by the module. These are exported here such that if you want to create your own methods using COOPS, and want to dispatch based on the type of graph, it is possible to do so.
NOTE: <multigraph> is a subclass of <multidigraph>, and <graph> is a subclass of <digraph>. These all inherit from an <abstract-graph> class, but multigraph types and graph types are otherwise not related in any specific hierarchical way. The reason for this is that this model makes it easier to dispatch based on the possibility of multiple edges vs. dispatching on whether or not the edges are directed.
Constructors
[procedure] (make-multidigraph [#!rest attr])[procedure] (make-multigraph [#!rest attr])
[procedure] (make-digraph [#!rest attr])
[procedure] (make-graph [#!rest attr])
Default procedures for creating and initializing (multi)(di)graph objects.
- attr
- A series of keyword / value pairs for graph attributes. For example, you may name your graph "Graph 1", which would be called as seen below. Note that keyword types are necessary (symbol ending with ':'), so you can't add attributes with keys of type symbol, string, etc.
(define G (make-graph name: "Graph 1"))
General methods
[method] (graph-copy (G <graph>))Creates a shallow copy of (multi)(di)graph G, depending on the type of G. Deep copies are not provided, but they are typically unnecessary unless you are writing directly to a slot value or using set! haphazardly in your code. Using the methods provided by the graphs egg for primitive operations such as adding or removing vertices and edges should nullify this issue.
This may not seem like a terribly useful method to have; however, using this in conjunction with destructive operations can help hide side effects on graphs. Because non-destructive operations have to construct a copy of the graph for small changes, they can have larger performance implications down the line (adding 1 vertex to a graph with 100000 vertices means you have to first copy the original graph and then add 1 vertex to that copy destructively). If anybody has any ideas for constructing an efficient functional hash-table or graph type, please let me know, as currently this implementation utilizes srfi-69, which provides traditional (and very imperative) hash-tables.
[method] (graph->list (G <graph>)) [[DEPRECATED]]NOTE: As of Release 0.4 this function is deprecated. It will likely be removed in the coming releases.
Converts a graph-type object into an adjacency-list type format. Currently provides something along the lines of:
'((a {(b . <hash-table>) (c . <hash-table>)}) (b {(a . <hash-table>)}) (c {(a . <hash-table>)}))
Where each hash-table is the properties of the edge. This was initially done to provide as sane means of displaying the adjacency table, but should not be relied upon in most cases.
[method] (graph-attribute (G <graph>) keyword)Allows one to retrieve the value of the attribute corresponding to keyword in the graph G.
- G
- the graph to search
- keyword
- the keyword corresponding to the value you wish to retrieve (e.g. name:)
[method] (graph-attribute-set! (G <graph>) keyword value)
Sets an attribute for graph G.
- G
- the graph to set the attribute
- keyword
- some keyword which satisfies keyword?
- value
- any value for which keyword should correspond
Predicate for checking if a vertex exists within graph G.
- G
- the graph to check
- vertex
- the identifier for the vertex (can be anything)
[method] (graph-adjacent? (G <multigraph>) u v [id])
[method] (graph-adjacent? (G <digraph>) u v)
[method] (graph-adjacent? (G <graph>) u v)
Predicate to check if an edge exists between vertices u and v (in other words, is v adjacent to u?). For multigraph types, an optional id argument can also be passed, to differentiate between multiple edges.
- G
- the graph to check
- u
- the head vertex
- v
- the tail vertex
- id
- an optional argument for multigraph-types which allows one to specify which edge you may be looking for (where id is a unique identifier). Passing id when calling this method with a graph type will result in an error.
[method] (graph-neighbours (G <multigraph>) vertex)
[method] (graph-neighbours (G <digraph>) vertex)
[method] (graph-neighbours (G <graph>) vertex)
Returns the set or multiset of adjacent vertices to the provided vertex. If called with a multigraph type, the multiset contains pair-lists with the adjacent vertex identifier as well as the unique id field for each edge. E.g. if vertex a has two edges to b, labeled with id 1 and 2 respectively, the multiset returned will be {(b 1) (b 2)}. If this was instead a regular graph, then the returned set would be {b}.
- G
- the graph to search neighbours within
- vertex
- the vertex whose neighbours you wish to receive
NOTE: raises an error if the vertex does not exist.
[procedure] (graph? G)Predicate for testing whether or not a value is a graph type. This does NOT test that an object is of type <class <graph>>, but whether something is a graph at all. Therefore, this returns #t iff the type of G is <multidigraph>, <multigraph>, <digraph>, <graph>, or some descendent thereof, else returns #f.
- G
- the object to test
Predicate for testing whether or not a value is a directed graph. Returns #t iff the type of G is <multidigraph> or <digraph>. Otherwise returns #f.
- G
- the object to test
Predicate for testing if a graph is a multigraph (a graph that can allow multiple edges). Returns #t iff G is of type <multidigraph>, <multigraph>, or some descendent thereof. Otherwise returns #f.
- G
- the object to test
Vertex methods
[method] (graph-vertex (G <graph>) vertex)Returns a hash table of the attributes for a vertex in G.
- G
- the graph to search
- vertex
- the vertex of which you wish to get the attributes of
Returns a set V containing all the vertices in graph G.
- G
- the graph whose vertices you want.
[method] (graph-vertex-add! (G <graph>) vertex [#!rest attr])
Adds a vertex with attributes attr to a graph G. Raises an error if the vertex already exists within the graph.
- G
- the graph you wish to add a vertex to
- vertex
- the vertex (identifier) you wish to add
- attr
- a series of keyword / value pairs that define vertex attributes
[method] (graph-vertex-remove! (G <graph>) vertex)
Removes a vertex and all associated edges / vertex attributes from the graph G. Raises an error if the vertex does not exist within the graph.
- G
- the graph you wish to remove the vertex from
- vertex
- the vertex you wish to remove
[method] (graph-vertex-update! (G <graph>) vertex [#!rest attr])
Updates the vertex attributes for a vertex in graph G. Raises an error if the vertex does not exist within the graph.
- G
- the graph to update
- vertex
- the vertex whose attributes you wish to modify
- attr
- a series of keyword / value pairs which describe attributes to add to the vertex
E.g.:
(graph-vertex-update G 'a colour: "red" size: 4)
Edge methods
[method] (graph-edge (G <multidigraph>) u v id)[method] (graph-edge (G <multigraph>) u v id)
[method] (graph-edge (G <digraph>) u v)
[method] (graph-edge (G <graph>) u v)
Returns an alist of the attributes for edge u->v in G. If u->v does not exist in G, an error is raised.
- G
- the graph
- u
- the head vertex
- v
- the tail vertex
- id
- (multigraph-types only) distinguishes which edge amongst multiple edges
[method] (graph-edge-add (G <multigraph>) u v id [#!rest attr])
[method] (graph-edge-add (G <digraph>) u v [#!rest attr])
[method] (graph-edge-add (G <graph>) u v [#!rest attr])
[method] (graph-edge-add! (G <multidigraph>) u v id [#!rest attr])
[method] (graph-edge-add! (G <multigraph>) u v id [#!rest attr])
[method] (graph-edge-add! (G <digraph>) u v [#!rest attr])
[method] (graph-edge-add! (G <graph>) u v [#!rest attr])
Adds an edge u->v to the graph G. Optional attributes can be added as keyword / value pairs in attr. Note that for multigraph-types, an identifier (id) is needed to distinguish edges (can be anything that compares with equal?). For undirected graph types this adds u->v and v->u to the graph since the distinction is meaningless.
Raises an error if the edge already exists or already exists with the ID id. If either of the vertices u or v do not exist when this method is called, they are first added to the graph, and the edge added afterwards.
- G
- the graph to add the edge to
- u
- the head vertex
- v
- the tail vertex
- id
- an identifier (compares with equal?) for the edge (in cases where multiple edges can exist). NOTE: Do not set id to be #f, as this can cause problems with removing edges later, and makes no logical sense.
- attr
- a list of keyword / value pairs
[method] (graph-edge-remove (G <multigraph>) u v [id])
[method] (graph-edge-remove (G <digraph>) u v)
[method] (graph-edge-remove (G <graph>) u v)
[method] (graph-edge-remove! (G <multidigraph>) u v [id])
[method] (graph-edge-remove! (G <multigraph>) u v [id])
[method] (graph-edge-remove! (G <digraph>) u v)
[method] (graph-edge-remove! (G <graph>) u v)
Removes an edge u->v from the graph G. For multigraph types, if id is specified, it removes the edge u->v with ID id, otherwise removes all edges u->v. For undirected graphs, the edge u->v and v->u are both removed, as there is no distinction between the two.
Raises an error if the edge u->v does not exist in graph G.
- G
- the graph to remove the edge from
- u
- the head vertex
- v
- the tail vertex
- id
- (multigraph-types only) Specifies which edge with this identifier to remove, or removes all edges u->v if this is #f or not passed in.
[method] (graph-edge-update (G <multigraph>) u v id [#!rest attr])
[method] (graph-edge-update (G <digraph>) u v [#!rest attr])
[method] (graph-edge-update (G <graph>) u v [#!rest attr])
[method] (graph-edge-update! (G <multidigraph>) u v id [#!rest attr])
[method] (graph-edge-update! (G <multigraph>) u v id [#!rest attr])
[method] (graph-edge-update! (G <digraph>) u v [#!rest attr])
[method] (graph-edge-update! (G <graph>) u v [#!rest attr])
Updates the attributes for an edge u->v in graph G, with ID id if the graph is a multigraph. Raises an error if the edge u->v does not exist.
- G
- the graph to update
- u
- the head vertex of the edge to update
- v
- the tail vertex of the edge to update
- id
- (multigraph-types only) the ID of the edge whose attributes you wish to update
- attr
- a set of keyword / value pairs describing attributes to add to the edge
Other methods
[method] (graph-simple? (G <graph>))Predicate for testing whether or not a graph G is simple. A graph is considered simple if it does not contain multiple edges and has no loops (i.e. edges that go from u->u). Given this definition, even if a graph has a type of <multidigraph> or <multigraph>, so long as no vertices actually _contain_ multiple edges, graph-simple? can still evaluate to #t. If multiple edges or loops are present, then graph-simple? will evaluate to #f.
[method] (graph-indegree (G <digraph>) vertex)Computes the indegree of a vertex in a directed graph G. When called with an undirected graph, returns the same as graph-degree.
[method] (graph-outdegree (G <digraph>) vertex)Computes the outdegree of a vertex in a directed graph G. When called with an undirected graph, returns the same as graph-degree.
[method] (graph-degree (G <graph>) vertex)Computes the degree (number of edges coming in and out of) a vertex in a graph G.
[method] (graph-order (G <graph>))Returns the order of the graph. The order of a graph is defined as the number of unique vertices within a graph.
graphs-derived
Isomorphism
The set of procedures below implement the VF2 algorithm for graph/subgraph isomorphism checking. To be clear with how these work, a short explanation of the implementation is provided. Specifically, there is some confusion as to what is meant by "subgraph isomorphism." In some circles, it is treated as "there is no subgraph in G1 that is isomorphic to some other graph G2." Here, subgraph isomorphism is taken to mean subgraph isomorphism as treated by the authors of the original VF2 algorithm, that is, "a graph G1 is subgraph isomorphic to another graph G2 if there exists some subgraph in G1 which has a direct isomorphism to graph G2." In effect, this means that the order you input your graphs matters when testing for subgraph isomorphism.
The second major thing to bear in mind is the semantic-feasibility? argument that shows up in each of the procedures below. The original authors of the VF2 paper divided their evaluation of an isomorphism into two feasibility categories: semantic and syntactic. Syntactic feasibility defines a set of rules that evaluates whether the structure of a partial mapping between two graphs provides a feasible isomorphism. However, the authors did not specify a unique or specific way to determine semantic feasibility. The reason for this is because semantic feasibility deals with evaluating whether two vertices n and m can be added to the partial mapping s based on their attributes. In the original paper the attributes of their edges (weights, length, etc.) were evaluated as semantically feasible if they existed within some tolerance (to maintain a sense of scale). Consequently, this worked well for the application the authors had in mind, but means nothing for every isomorphism problem in general. Therefore, no particular semantic-feasibility? procedure is assumed, and the default is just a dummy procedure that always returns true (this means we effectively ignore semantic information about the vertices).
To construct a custom procedure to evaluate semantic feasibility, it should ideally be written as below:
[procedure] (semantic-feasibility? G1 G2 s n m) => boolEvaluates semantic feasibility of adding vertices n from G1 and m from G2 to the partial mapping s. This can be done by evaluating the vertex attributes between n and m, or by evaluating the edge attributes of n and its neighbours in G1 to that of m and its neighbours in G2.
- G1
- the first graph
- G2
- the second graph
- s
- the partial mapping between G1 and G2. Represented as a set of pairs (N . M) of feasible matches between G1 and G2
- n
- a candidate vertex from G1
- m
- a candidate vertex from G2
[procedure] (subgraph-isomorphisms G1 G2 #!optional semantic-feasibility?)
Returns a lazy-seq of all match sets between G1 and G2. Each isomorphism in the lazy-seq is represented as a set of pairs which lists the match between G1 and G2. If subgraph-isomorphisms is used instead, the algorithm attempts to find a subgraph in G1 that is isomorphic to G2.
- G1
- the first graph
- G2
- the second graph
- semantic-feasibility?
- a procedure which evaluates semantic feasibility for a candidate pair. See discussion above.
[procedure] (subgraph-isomorphisms-list G1 G2 #!optional semantic-feasibility?)
Same as above, however returns a list instead of a lazy-seq. I suggest avoiding these, as there may be many isomorphisms between two large graphs. As a result, the runtime of this is not necessarily known.
[procedure] (graph-isomorphic? G1 G2 #!optional semantic-feasibility?)[procedure] (subgraph-isomorphic? G1 G2 #!optional semantic-feasibility?)
Predicate which tests if a graph G1 is isomorphic or subgraph-isomorphic to G2.
- G1
- the first graph
- G2
- the second graph
- semantic-feasibility?
- a procedure which evaluates semantic feasibility for a candidate pair. See discussion above.
Repository
Examples
To come later. In the meantime, send an email to chicken-users or myself (you can find the email on Github) if you stumble into trouble.
Version History
- 0.4.5
- Adds random-bsd as a test-dependency, should have been in 0.4.4
- 0.4.4
- Fixes issues in tests, removes test-dependencies on data-generators and test-generative
- 0.4.3
- Bug fix for http://bugs.call-cc.org/ticket/1267, which broke due to the arrays egg updating the `sets` module to `array-sets`
- 0.4.2
- Bug fix for graph-edge-add regression when used with multidigraphs. Test cases added for make procedures and predicates
- 0.4.1
- Bug fix for graph-edge -> return a hash-table instead of an alist. Documentation fixes.
- 0.4
- Simplified implementation details for API and performance improvements for isomorphism functionality. graph-vertex returns a hash-table, not an alist.
- 0.3
- Adds graphs-derived module for isomorphism (VF2) functionality.
- 0.2
- Initial release to the coop under BSD3 license
License
Copyright (c) 2015, Jeremy Steward All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.