ubergraph

repository·master·Indexed 20 days ago

https://github.com/engelberg/ubergraph

A versatile, general-purpose graph data structure for Clojure that implements Loom protocols. It supports directed, undirected, and weighted edges, node and edge attributes, parallel edges, and mixed edge types. It provides four graph flavors: Graph, Digraph, Multigraph, and Multidigraph, along with advanced querying and shortest-path algorithms.

Tokens
6.3K
Snippets
19
Records
27
Agent score
20%

What's inside ubergraph

  1. Overview of Ubergraph

    master

    Ubergraph is a versatile, general-purpose graph data structure for Clojure. It is designed to be compatible with Loom protocols but provides advanced capabilities that exceed standard Loom implementations.

    Key features include:

    • Support for directed, undirected, and weighted edges.
    • Support for node and edge attributes.
    • Ability to mix directed and undirected edges within a single graph.
    • Support for multiple "parallel" edges between a pair of nodes.
    • Support for multiple weights per edge and changeable weights.
    • Full implementation of all Loom protocols.
  2. Identify edges in Multigraphs using Edge Descriptions

    master

    In a multigraph, multiple edges can exist between the same src and dest. To uniquely identify an edge, Ubergraph uses "edge descriptions".

    Edge Description Formats:

    • [src dest]
    • [src dest weight]
    • [src dest attribute-map]

    When using a description, Ubergraph will pick an edge that matches the description. If the description is unique (e.g., includes a specific color attribute), it will target that specific edge.

    Example:

    ;; If multiple edges exist between 1 and 4, this targets the red one:
    (weight g [1 4 {:color :red}])
    
    ;; Targets the edge from :a to :b with weight 5 to get its :color attribute:
    (attr g [:a :b 5] :color)
    (weight g [1 4 {:color :red}])
    (attr g [:a :b 5] :color)
  3. Search-driven graph generation with transition functions

    master

    You can perform searches on implicit or infinite graphs by passing a transition function to alg/shortest-path instead of a concrete graph object.

    A transition function must take a node as input and return a sequence of maps representing outgoing edges. Each map must contain a :dest key (the destination node) and can include other keys as edge attributes (e.g., :weight, :label).

    Example transition function for an infinite graph of natural numbers:

    (fn [n] [{:dest (* n 2) :label :double} 
             {:dest (inc n) :label :increment}])

    When searching via a transition function:

    • alg/edges-in-path returns edge descriptions as vectors [src dest attribute-map] rather than Edge objects.
    • You can use :node-filter to constrain the search space in infinite graphs.
    ;; Search on an infinite graph using a transition function
    (-> (alg/shortest-path (fn [n] [{:dest (* n 2) :label :double} 
                                       {:dest (inc n) :label :increment}])
                              {:start-node 1, :end-node 19, :cost-attr :weight})
           alg/edges-in-path)
  4. Understand Ubergraph's relationship to Loom

    master

    Ubergraph is designed to extend the capabilities of Loom, the primary graph library in the Clojure ecosystem. While Loom uses protocols to define graph behavior, many of its protocols and algorithms assume a single edge exists between any two nodes (identified by [src dest]).

    Ubergraph addresses these limitations by supporting:

    • Mixing directed and undirected edges.
    • Multiple edges between the same pair of nodes (multigraphs).
    • Multiple notions of 'cost' or weights associated with a single edge.

    Developers can use the ubergraph.alg namespace to access algorithms that have been curated, modified, or newly written to support these advanced graph structures. Note that while some Loom algorithms work on non-multigraph Ubergraphs, others (like minimum spanning tree or max flow) may require the specific adaptations found in ubergraph.alg to function correctly with multi-edges.

  5. Use Edge objects and the mirror-edge? protocol

    master

    Unlike Loom, which returns simple tuples, Ubergraph returns Edge or UndirectedEdge objects.

    Key Properties:

    • Every edge has a :src, :dest, and a unique :id (UUID) which points to its attribute map.
    • Undirected Edges: Stored internally as a pair of edge objects sharing the same ID. One edge is marked as a "mirror".

    Handling Unique Edges: To iterate over unique edges in an undirected graph without processing both directions of the same edge, use mirror-edge? with remove:

    (remove mirror-edge? (edges g))

    Edge Abstractions: When writing algorithms, use the following protocol functions on edge objects instead of destructuring tuples:

    • src: Get source node.
    • dest: Get destination node.
    • edge?, directed-edge?, undirected-edge?: Type checks.
    • other-direction: Returns the other edge in an undirected pair, or nil if directed.
    (-> (uber/graph [:a :b])
         (uber/add-directed-edges [:a :c])
         uber/edges)
    
    ;; Returns a sequence of Edge and UndirectedEdge objects
    ;; e.g. #ubergraph.core.UndirectedEdge{:id #uuid "...", :src :b, :dest :a, :mirror? true}
  6. Understand Ubergraph equality and data model constraints

    master

    Node Selection

    Nodes are used as keys in Clojure maps. Use values that work as hash map keys (numbers, strings, keywords, immutable vectors/maps/sets). Warning: Avoid using nil or false as node values, as they can cause incorrect results in Ubergraph functions.

    Edge Representation

    Edges are represented as a pair of nodes plus an internally generated UUID. This UUID uniquely identifies the edge's attribute map. While edge objects are immutable, do not add edge objects as nodes to any graph.

    Graph Equality

    Ubergraph overrides equality (=) to provide an intuitive semantic comparison rather than a structural one. Two graphs g1 and g2 are equal if:

    1. They have the same set of node values.
    2. For every node, the attribute maps are equal.
    3. For every pair of nodes, the edges between them (ignoring UUIDs but including directionality and attribute maps) are equal. In multigraphs, edges are compared as multisets.

    Note: Ubergraph equality is not Graph Isomorphism. Two isomorphic graphs with different node labels will return false for =.

  7. Understand the four Ubergraph flavors

    master

    Ubergraph provides four distinct graph types based on how they handle edge directionality and parallel edges. While they share the same underlying representation, their default behaviors differ:

    TypeDefault DirectionAllows Parallel Edges
    MultidigraphDirectedYes
    DigraphDirectedNo
    MultigraphUndirectedYes
    GraphUndirectedNo

    Note: You can override these defaults. For example, you can add directed edges to an undirected graph using add-directed-edges, or undirected edges to a directed digraph using add-undirected-edges.

  8. Perform graph traversal from a source node

    master

    If you provide only a :start-node to alg/shortest-path, it returns an AllPathsFromSource object. This acts as an efficient lookup table for all shortest paths emanating from that source.

    Key Operations

    • Find path to specific node: Use alg/path-to with the lookup table.
    • List all reachable nodes: Use alg/all-destinations.
    • Sequence of discovery: Set :traverse true to get a sequence of path objects in the order they were discovered by the search algorithm.
    • Constrained Traversal: When using :traverse true, you can use :min-cost and :max-cost to filter the sequence of discovered paths.
    (def out-of-coulton (alg/shortest-path airports {:start-node :Coulton, :cost-attr :distance}))
    
    ;; Get path to a specific node
    (alg/path-to out-of-coulton :Artemis)
    
    ;; Get all reachable destinations
    (alg/all-destinations out-of-coulton)
  9. Understand Ubergraph serialization options

    master

    When serializing or representing graph data, you can choose between two conceptual approaches depending on whether you need to preserve identity or minimize footprint:

    • Option 1 (Reconstructible): Stores only the sufficient information required to rebuild an equivalent ubergraph. This approach is more compact and highly compatible with various serialization libraries. Note that in this mode, UUIDs for nodes and edges will change upon reconstruction, though the underlying nodes, edges, and attributes will remain identical in content.
    • Option 2 (Identity-Preserving): Captures the exact UUIDs from the existing graph and preserves any cached hash values. Use this option when maintaining specific node/edge identities across serialization cycles is required.
  10. Quickstart: Requiring Ubergraph

    master

    Require ubergraph.core in your namespace. For convenience, ubergraph.core provides access to all of Loom's protocol functions through its own namespace, allowing you to call functions like out-edges or add-attr directly from the uber alias instead of importing multiple Loom namespaces.

    (ns example.core
      (:require [ubergraph.core :as uber]))
  11. Install Ubergraph via Leiningen

    master

    To use Ubergraph in your Clojure project, add it to your project.clj dependencies. Note that Ubergraph depends on Loom, so Loom will be automatically downloaded and available in your project.

    [ubergraph "0.9.0"]
  12. Visualize graphs with viz-graph

    master

    Use uber/viz-graph from the ubergraph.core namespace to create a visualization of your graph. This requires GraphViz to be installed on your system.

    Basic Usage

    (uber/viz-graph airports)

    Saving to a file

    Pass an options map with a :save key containing :filename and :format (e.g., :png, :pdf).

    (uber/viz-graph airports {:save {:filename "C:/temp/airports.png" :format :png}})

    Customization Options

    • Layout Algorithm: Specify a Graphviz layout using :layout (e.g., :neato).
    • Auto-labeling: Use :auto-label true to annotate all nodes and edges with their attribute maps.
    • Attribute Integration: If node or edge attribute maps contain Graphviz-compatible keys (like :color or :style), Graphviz will automatically use them for drawing.
    (uber/viz-graph airports {:save {:filename "C:/temp/airports.png" :format :png}})