Documentation
¶
Overview ¶
Package gonx is a performance-oriented graph library for Go, in the spirit of Python's networkx but built around dense integer node IDs and a compact, cache-friendly representation.
The library separates mutation from reading. A Builder accumulates nodes and edges, and Builder.Build freezes it into an immutable Graph stored in Compressed Sparse Row (CSR) form. The CSR layout gives zero-copy, O(1) neighbor iteration, which is the dominant access pattern for the simulations and graph metrics this library targets. Digraph and DigraphBuilder are the directed counterparts; a Digraph stores both edge directions in CSR form, so out-neighbors and in-neighbors are equally cheap to walk.
Node IDs are dense integers in the range [0, N). Graphs are always simple (no self-loops or duplicate edges). Edges are unweighted unless the builder was given weights, through Builder.AddEdgeW or NewWeightedBuilder; a weighted graph stores one float64 per edge in an array laid out exactly like the adjacency, so the weight of Neighbors(u)[i] is Weights(u)[i], and an unweighted graph stores no weight array. Adjacency is the read-only view of each node's outgoing edges that Graph and Digraph share, for algorithms that only walk edges in their natural direction.
All randomized operations take an explicit *math/rand/v2.Rand so results are fully reproducible; the package never touches a global RNG.
Example ¶
package main
import (
"fmt"
"github.com/LuisLSousa/gonx"
)
func main() {
// Assemble a topology with Builder, then freeze it into an immutable CSR
// Graph for reading.
b := gonx.NewBuilder(4)
b.AddEdge(0, 1)
b.AddEdge(0, 2)
b.AddEdge(2, 3)
g := b.Build()
fmt.Println(g.NumNodes(), g.NumEdges())
fmt.Println(g.Neighbors(0))
fmt.Println(g.HasEdge(1, 2))
}
Output: 4 3 [1 2] false
Index ¶
- Variables
- func NewRand(seed uint64) *rand.Rand
- type Adjacency
- type Builder
- func (b *Builder) AddEdge(u, v int) bool
- func (b *Builder) AddEdgeUnchecked(u, v int) bool
- func (b *Builder) AddEdgeW(u, v int, w float64) bool
- func (b *Builder) AddNode() int
- func (b *Builder) Build() *Graph
- func (b *Builder) Degree(u int) int
- func (b *Builder) HasEdge(u, v int) bool
- func (b *Builder) NumEdges() int
- func (b *Builder) NumNodes() int
- func (b *Builder) RemoveEdge(u, v int) bool
- func (b *Builder) Weighted() bool
- type Digraph
- func (g *Digraph) Degree(u int) int
- func (g *Digraph) EdgeIndex(u, v int) (slot int, ok bool)
- func (g *Digraph) Edges() iter.Seq2[int, int]
- func (g *Digraph) HasEdge(u, v int) bool
- func (g *Digraph) HasNegativeWeight() bool
- func (g *Digraph) InDegree(u int) int
- func (g *Digraph) InEdgeIndex(u, v int) (slot int, ok bool)
- func (g *Digraph) InEdgeOffset(u int) int
- func (g *Digraph) InNeighbors(u int) []int32
- func (g *Digraph) InNeighborsSeq(u int) iter.Seq[int]
- func (g *Digraph) InWeights(u int) []float64
- func (g *Digraph) Nodes() iter.Seq[int]
- func (g *Digraph) NumEdges() int
- func (g *Digraph) NumNodes() int
- func (g *Digraph) OutDegree(u int) int
- func (g *Digraph) OutEdgeOffset(u int) int
- func (g *Digraph) OutNeighbors(u int) []int32
- func (g *Digraph) OutNeighborsSeq(u int) iter.Seq[int]
- func (g *Digraph) OutWeights(u int) []float64
- func (g *Digraph) RandomInNeighbor(u int, r *rand.Rand) (v int, ok bool)
- func (g *Digraph) RandomOutNeighbor(u int, r *rand.Rand) (v int, ok bool)
- func (g *Digraph) ToBuilder() *DigraphBuilder
- func (g *Digraph) Weight(u, v int) (w float64, ok bool)
- func (g *Digraph) Weighted() bool
- type DigraphBuilder
- func (b *DigraphBuilder) AddEdge(u, v int) bool
- func (b *DigraphBuilder) AddEdgeUnchecked(u, v int) bool
- func (b *DigraphBuilder) AddEdgeW(u, v int, w float64) bool
- func (b *DigraphBuilder) AddNode() int
- func (b *DigraphBuilder) Build() *Digraph
- func (b *DigraphBuilder) HasEdge(u, v int) bool
- func (b *DigraphBuilder) InDegree(u int) int
- func (b *DigraphBuilder) NumEdges() int
- func (b *DigraphBuilder) NumNodes() int
- func (b *DigraphBuilder) OutDegree(u int) int
- func (b *DigraphBuilder) RemoveEdge(u, v int) bool
- func (b *DigraphBuilder) Weighted() bool
- type Graph
- func (g *Graph) Degree(u int) int
- func (g *Graph) EdgeIndex(u, v int) (slot int, ok bool)
- func (g *Graph) EdgeOffset(u int) int
- func (g *Graph) Edges() iter.Seq2[int, int]
- func (g *Graph) HasEdge(u, v int) bool
- func (g *Graph) HasNegativeWeight() bool
- func (g *Graph) Neighbors(u int) []int32
- func (g *Graph) NeighborsSeq(u int) iter.Seq[int]
- func (g *Graph) Nodes() iter.Seq[int]
- func (g *Graph) NumEdges() int
- func (g *Graph) NumNodes() int
- func (g *Graph) OutNeighbors(u int) []int32
- func (g *Graph) OutWeights(u int) []float64
- func (g *Graph) RandomNeighbor(u int, r *rand.Rand) (v int, ok bool)
- func (g *Graph) ToBuilder() *Builder
- func (g *Graph) Weight(u, v int) (w float64, ok bool)
- func (g *Graph) Weighted() bool
- func (g *Graph) Weights(u int) []float64
- type Restricted
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidParam indicates a generator, transform, or metric was given // parameters it cannot work with (e.g. odd degree for Watts-Strogatz). ErrInvalidParam = errors.New("gonx: invalid parameter") // ErrNotPermutation indicates a relabeling slice is not a permutation of [0, N). ErrNotPermutation = errors.New("gonx: not a permutation of node ids") )
Sentinel errors returned by constructors and algorithms. Wrap-friendly: callers may test with errors.Is.
Functions ¶
Types ¶
type Adjacency ¶ added in v1.2.0
type Adjacency interface {
NumNodes() int
OutNeighbors(u int) []int32
OutWeights(u int) []float64
Weighted() bool
HasNegativeWeight() bool
// contains filtered or unexported methods
}
Adjacency is the read-only view of a graph's outgoing edges that Graph and Digraph share, and that a Restricted view of either provides too: how many nodes there are and, for each node, the edges leaving it together with their weights when the graph has any. An algorithm that only ever walks edges in their natural direction takes an Adjacency and runs on either graph kind, or on a view with parts hidden, from a single implementation. On an undirected Graph the edges leaving u are its neighbors, since every edge can be walked from either end; on a Digraph only the out-side is part of the view, and the in-lists stay on the concrete type.
The contract, which every implementation honors and which algorithms rely on:
- Node IDs are dense in [0, NumNodes).
- OutNeighbors(u) lists the targets of u's outgoing edges in ascending order, without duplicates and without u itself.
- Weighted is constant for the life of the graph. When it is true, OutWeights(u) has the same length as OutNeighbors(u) and OutWeights(u)[i] is the finite weight of the edge to OutNeighbors(u)[i]; when it is false, OutWeights returns nil and every edge counts as weight 1.
- HasNegativeWeight is true if, and only if, some OutWeights entry is below zero, and like Weighted it costs O(1).
- Returned slices are views into the graph's storage: read-only, with no spare capacity, and valid for as long as the graph is. They may be held across calls, and the graph may be read from several goroutines at once.
Adjacency is sealed: one of its methods is unexported, so only this package can define a full implementation, and the method set can grow with the algorithms that consume it. A later release may open it to other representations once that set has settled. A type that embeds one of this package's types, or an Adjacency, still satisfies the interface, and takes on the contract for every method it overrides; an override of OutWeights, for one, must keep HasNegativeWeight truthful, since Dijkstra relies on it to reject negative weights up front. RestrictedView, which builds on a graph's storage rather than its methods, accepts only this package's own types.
Example ¶
package main
import (
"fmt"
"github.com/LuisLSousa/gonx"
)
func main() {
// One function for both graph kinds: the total weight leaving each node.
outWeight := func(g gonx.Adjacency) []float64 {
out := make([]float64, g.NumNodes())
for u := range out {
if g.Weighted() {
for _, w := range g.OutWeights(u) {
out[u] += w
}
} else {
out[u] = float64(len(g.OutNeighbors(u)))
}
}
return out
}
ub := gonx.NewBuilder(3)
ub.AddEdgeW(0, 1, 2)
ub.AddEdgeW(1, 2, 3)
db := gonx.NewDigraphBuilder(3)
db.AddEdgeW(0, 1, 2)
db.AddEdgeW(1, 2, 3)
fmt.Println(outWeight(ub.Build())) // an undirected edge leaves both ends
fmt.Println(outWeight(db.Build()))
}
Output: [2 5 3] [2 3 0]
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder is a mutable undirected graph used to assemble a topology before freezing it into an immutable Graph. It is not safe for concurrent use.
Edge methods (AddEdge, RemoveEdge, HasEdge) treat out-of-range endpoints as absent edges and report false; Degree panics on an out-of-range node.
A Builder from NewBuilder starts unweighted, and the first successful Builder.AddEdgeW call switches it to weighted for good; one from NewWeightedBuilder is weighted from the start. See AddEdgeW for how edges without an explicit weight are treated.
func NewBuilder ¶
NewBuilder returns a Builder with n isolated nodes (IDs 0..n-1). It panics if n exceeds 2^31-1, the maximum node count supported by the int32 CSR layout.
func NewWeightedBuilder ¶ added in v1.2.0
NewWeightedBuilder returns a Builder with n isolated nodes that is weighted from the start: every edge it ever holds has a weight, 1 unless AddEdgeW gave it another, and the Graph it builds reports Weighted even with no edges. Use it when the graph is weighted by nature; a plain NewBuilder becomes weighted on its first successful AddEdgeW call instead. It panics if n exceeds 2^31-1.
func (*Builder) AddEdge ¶
AddEdge inserts the undirected edge {u, v}. It returns false (and does nothing) for self-loops, out-of-range endpoints, or edges that already exist, so the resulting graph is always simple.
func (*Builder) AddEdgeUnchecked ¶
AddEdgeUnchecked inserts the undirected edge {u, v} without checking whether it already exists. Endpoints are still validated and self-loops rejected (returning false), but inserting an edge that is already present corrupts the Builder: the graph silently becomes a multigraph with a double-counted NumEdges. Use it only when each pair is known to be produced at most once — e.g. generators that enumerate pairs with u < v — where skipping the duplicate scan turns dense O(n*m) builds into O(m).
func (*Builder) AddEdgeW ¶ added in v1.2.0
AddEdgeW inserts the undirected edge {u, v} with weight w. It rejects everything AddEdge rejects, plus NaN and infinite weights, returning false in each case. Any finite weight is accepted, zero and negative values included; algorithms that need non-negative weights say so and check for themselves.
The first successful AddEdgeW call makes a NewBuilder weighted. Every edge already present then has weight 1, and edges added afterwards with AddEdge or AddEdgeUnchecked get weight 1 too, so a weighted graph has a weight for every edge; 1 is also the default networkx algorithms use for a missing weight attribute. An existing edge is never re-weighted: AddEdgeW on it returns false like AddEdge does, and changing a weight means RemoveEdge followed by AddEdgeW.
Example ¶
package main
import (
"fmt"
"github.com/LuisLSousa/gonx"
)
func main() {
// A weighted triangle. Weights ride along with their edges through Build and
// come back aligned with Neighbors.
b := gonx.NewBuilder(3)
b.AddEdgeW(0, 1, 2.5)
b.AddEdgeW(1, 2, 0.5)
b.AddEdge(0, 2) // an unweighted add on a weighted builder means weight 1
g := b.Build()
fmt.Println(g.Neighbors(1), g.Weights(1))
fmt.Println(g.Weight(0, 2))
fmt.Println(g.Weight(1, 1))
}
Output: [0 2] [2.5 0.5] 1 true 0 false
func (*Builder) AddNode ¶
AddNode appends a new isolated node and returns its ID. It panics if the node count would exceed 2^31-1.
func (*Builder) Build ¶
Build freezes the Builder into an immutable CSR Graph. Neighbor lists are sorted so the resulting Graph supports binary-search edge tests and has a canonical, deterministic layout; on a weighted Builder each weight travels with its edge. The Builder may be reused afterwards.
Build panics if the total adjacency size (2 * edges) exceeds 2^31-1, the capacity of the int32 CSR offsets.
func (*Builder) Degree ¶
Degree returns the number of neighbors of u. It panics if u is out of range.
func (*Builder) HasEdge ¶
HasEdge reports whether the undirected edge {u, v} exists. O(min(deg(u), deg(v))).
func (*Builder) RemoveEdge ¶
RemoveEdge deletes the undirected edge {u, v}, returning whether it existed.
func (*Builder) Weighted ¶ added in v1.2.0
Weighted reports whether the Builder carries edge weights: always for a NewWeightedBuilder, and from the first successful Builder.AddEdgeW call on for a NewBuilder.
type Digraph ¶ added in v1.1.0
type Digraph struct {
// contains filtered or unexported fields
}
Digraph is an immutable, directed graph stored in Compressed Sparse Row form, twice: the out-neighbors of node u occupy outData[outOffsets[u]:outOffsets[u+1]] and its in-neighbors mirror that in a second CSR, both sorted ascending. Storing both directions costs 2x the edge memory and buys O(1) access from either end, which is what reverse-flow algorithms (PageRank pulls rank from in-neighbors) and "who links here" queries need. On a weighted Digraph each direction has a weight array with the same indices as its adjacency. A Digraph is safe for concurrent reads.
In networkx terms, OutNeighbors are a node's successors and InNeighbors its predecessors.
Accessors that take a node ID panic with a descriptive message when the ID is outside [0, N); HasEdge, Weight, EdgeIndex and InEdgeIndex take an edge instead and report false for out-of-range endpoints. Slices returned by the accessors are views into the graph's storage with no spare capacity. The zero value is not a valid Digraph; obtain one from DigraphBuilder.Build.
Example ¶
package main
import (
"fmt"
"github.com/LuisLSousa/gonx"
)
func main() {
// A tiny citation graph: later papers cite earlier ones. Direction
// matters, and a Digraph keeps both adjacency directions, so asking
// "whom does 3 cite?" and "who cites 0?" are equally cheap.
b := gonx.NewDigraphBuilder(4)
b.AddEdge(1, 0)
b.AddEdge(2, 0)
b.AddEdge(3, 0)
b.AddEdge(3, 2)
g := b.Build()
fmt.Println(g.OutNeighbors(3)) // what 3 cites
fmt.Println(g.InNeighbors(0)) // who cites 0
fmt.Println(g.HasEdge(3, 2), g.HasEdge(2, 3))
}
Output: [0 2] [1 2 3] true false
func (*Digraph) Degree ¶ added in v1.1.0
Degree returns InDegree(u) + OutDegree(u), matching networkx's DiGraph.degree. It panics if u is out of range.
func (*Digraph) EdgeIndex ¶ added in v1.2.0
EdgeIndex returns the out slot of the edge u->v: OutEdgeOffset(u) plus the position of v in OutNeighbors(u). ok is false when the edge does not exist or an endpoint is out of range. It runs in O(log outdeg(u)).
func (*Digraph) Edges ¶ added in v1.1.0
Edges iterates over each directed edge exactly once as (u, v), ordered by source and then by target.
func (*Digraph) HasEdge ¶ added in v1.1.0
HasEdge reports whether the directed edge u->v exists. It binary-searches the shorter of u's out-list and v's in-list, so it runs in O(log deg).
func (*Digraph) HasNegativeWeight ¶ added in v1.2.0
HasNegativeWeight reports whether any edge weighs less than zero. It is always false on an unweighted graph. Build records the answer, so the call is O(1); algorithms that need non-negative weights check it once up front.
func (*Digraph) InDegree ¶ added in v1.1.0
InDegree returns the number of incoming edges of u. It panics if u is out of range.
func (*Digraph) InEdgeIndex ¶ added in v1.2.0
InEdgeIndex returns the in slot of the edge u->v: InEdgeOffset(v) plus the position of u in InNeighbors(v). ok is false when the edge does not exist or an endpoint is out of range. It runs in O(log indeg(v)).
func (*Digraph) InEdgeOffset ¶ added in v1.2.0
InEdgeOffset is the in-edge counterpart of Digraph.OutEdgeOffset: entry i of InNeighbors(u) sits at position InEdgeOffset(u)+i in an array of length NumEdges indexed like the in-adjacency. The two indexings are unrelated; an edge's out slot and in slot differ. It panics if u is out of range.
func (*Digraph) InNeighbors ¶ added in v1.1.0
InNeighbors returns the sources of u's incoming edges as a sorted, zero-copy slice into the graph's backing storage. Callers MUST NOT modify the returned slice. It panics if u is out of range.
func (*Digraph) InNeighborsSeq ¶ added in v1.1.0
InNeighborsSeq iterates over the sources of u's incoming edges in ascending order as ints. Hot paths should prefer Digraph.InNeighbors, which exposes the backing slice with no per-element call overhead. It panics if u is out of range.
func (*Digraph) InWeights ¶ added in v1.2.0
InWeights returns the weights of u's incoming edges, aligned index for index with Digraph.InNeighbors. The slice is zero-copy and MUST NOT be modified. It is nil when the graph is unweighted. It panics if u is out of range.
func (*Digraph) OutDegree ¶ added in v1.1.0
OutDegree returns the number of outgoing edges of u. It panics if u is out of range.
func (*Digraph) OutEdgeOffset ¶ added in v1.2.0
OutEdgeOffset returns the index at which u's outgoing edges start in the graph's out-edge arrays: entry i of OutNeighbors(u), and of OutWeights(u), sits at position OutEdgeOffset(u)+i in an array of length NumEdges. See Graph.EdgeOffset for what this is for. It panics if u is out of range.
func (*Digraph) OutNeighbors ¶ added in v1.1.0
OutNeighbors returns the targets of u's outgoing edges as a sorted, zero-copy slice into the graph's backing storage. Callers MUST NOT modify the returned slice. It panics if u is out of range.
func (*Digraph) OutNeighborsSeq ¶ added in v1.1.0
OutNeighborsSeq iterates over the targets of u's outgoing edges in ascending order as ints. Hot paths should prefer Digraph.OutNeighbors, which exposes the backing slice with no per-element call overhead. It panics if u is out of range.
Example ¶
package main
import (
"fmt"
"github.com/LuisLSousa/gonx"
)
func main() {
b := gonx.NewDigraphBuilder(4)
b.AddEdge(0, 3)
b.AddEdge(0, 1)
sum := 0
g := b.Build()
for v := range g.OutNeighborsSeq(0) {
sum += v
}
fmt.Println(sum)
}
Output: 4
func (*Digraph) OutWeights ¶ added in v1.2.0
OutWeights returns the weights of u's outgoing edges, aligned index for index with Digraph.OutNeighbors. The slice is zero-copy and MUST NOT be modified. It is nil when the graph is unweighted. It panics if u is out of range.
func (*Digraph) RandomInNeighbor ¶ added in v1.1.0
RandomInNeighbor returns a uniformly random source of u's incoming edges. ok is false when u has none. It panics if u is out of range.
func (*Digraph) RandomOutNeighbor ¶ added in v1.1.0
RandomOutNeighbor returns a uniformly random target of u's outgoing edges. ok is false when u has none. It panics if u is out of range.
func (*Digraph) ToBuilder ¶ added in v1.1.0
func (g *Digraph) ToBuilder() *DigraphBuilder
ToBuilder returns a mutable copy of the graph, weights included.
func (*Digraph) Weight ¶ added in v1.2.0
Weight returns the weight of the directed edge u->v. ok is false when the edge does not exist or an endpoint is out of range. On an unweighted graph every existing edge has weight 1. Like Digraph.HasEdge it binary-searches the shorter of u's out-list and v's in-list, so it runs in O(log deg).
type DigraphBuilder ¶ added in v1.1.0
type DigraphBuilder struct {
// contains filtered or unexported fields
}
DigraphBuilder is a mutable directed graph used to assemble a topology before freezing it into an immutable Digraph. It is not safe for concurrent use.
Like Builder, it keeps the graph simple: self-loops and duplicate edges are rejected. The directed edge u->v and its reverse v->u are distinct edges and may coexist.
Edge methods (AddEdge, RemoveEdge, HasEdge) treat out-of-range endpoints as absent edges and report false; the degree accessors panic on an out-of-range node.
A DigraphBuilder from NewDigraphBuilder starts unweighted, and the first successful DigraphBuilder.AddEdgeW call switches it to weighted for good; one from NewWeightedDigraphBuilder is weighted from the start. The rules are those of Builder.AddEdgeW.
func NewDigraphBuilder ¶ added in v1.1.0
func NewDigraphBuilder(n int) *DigraphBuilder
NewDigraphBuilder returns a DigraphBuilder with n isolated nodes (IDs 0..n-1). It panics if n exceeds 2^31-1, the maximum node count supported by the int32 CSR layout.
func NewWeightedDigraphBuilder ¶ added in v1.2.0
func NewWeightedDigraphBuilder(n int) *DigraphBuilder
NewWeightedDigraphBuilder returns a DigraphBuilder with n isolated nodes that is weighted from the start, the directed counterpart of NewWeightedBuilder. It panics if n exceeds 2^31-1.
func (*DigraphBuilder) AddEdge ¶ added in v1.1.0
func (b *DigraphBuilder) AddEdge(u, v int) bool
AddEdge inserts the directed edge u->v. It returns false (and does nothing) for self-loops, out-of-range endpoints, or edges that already exist, so the resulting graph is always simple. Inserting v->u afterwards is a distinct edge and succeeds. The duplicate check is DigraphBuilder.HasEdge, so adding many edges out of one node to targets that already have in-edges costs time quadratic in that node's out-degree; DigraphBuilder.AddEdgeUnchecked skips the check when the input is known to be simple.
func (*DigraphBuilder) AddEdgeUnchecked ¶ added in v1.1.0
func (b *DigraphBuilder) AddEdgeUnchecked(u, v int) bool
AddEdgeUnchecked inserts the directed edge u->v without checking whether it already exists. Endpoints are still validated and self-loops rejected (returning false), but inserting an edge that is already present corrupts the Builder: the graph silently becomes a multigraph with a double-counted NumEdges. Use it only when each ordered pair is known to be produced at most once, where skipping the duplicate scan turns dense O(n*m) builds into O(m).
func (*DigraphBuilder) AddEdgeW ¶ added in v1.2.0
func (b *DigraphBuilder) AddEdgeW(u, v int, w float64) bool
AddEdgeW inserts the directed edge u->v with weight w. It rejects everything AddEdge rejects, plus NaN and infinite weights, returning false in each case. The first successful call makes a NewDigraphBuilder weighted: edges already present then have weight 1, as do edges added later without an explicit weight, and an existing edge is never re-weighted. See Builder.AddEdgeW.
func (*DigraphBuilder) AddNode ¶ added in v1.1.0
func (b *DigraphBuilder) AddNode() int
AddNode appends a new isolated node and returns its ID. It panics if the node count would exceed 2^31-1.
func (*DigraphBuilder) Build ¶ added in v1.1.0
func (b *DigraphBuilder) Build() *Digraph
Build freezes the Builder into an immutable CSR Digraph, materializing both adjacency directions: the out-lists directly, the in-lists by a counting pass over them. Both are sorted, so the resulting Digraph supports binary-search edge tests and has a canonical, deterministic layout. On a weighted Builder each weight travels with its edge into both directions. The Builder may be reused afterwards.
Build panics if the edge count exceeds 2^31-1, the capacity of the int32 CSR offsets.
func (*DigraphBuilder) HasEdge ¶ added in v1.1.0
func (b *DigraphBuilder) HasEdge(u, v int) bool
HasEdge reports whether the directed edge u->v exists: O(1) when v has no in-edges, and otherwise O(outdeg(u)).
func (*DigraphBuilder) InDegree ¶ added in v1.1.0
func (b *DigraphBuilder) InDegree(u int) int
InDegree returns the number of incoming edges of u. It panics if u is out of range.
func (*DigraphBuilder) NumEdges ¶ added in v1.1.0
func (b *DigraphBuilder) NumEdges() int
NumEdges reports the number of directed edges.
func (*DigraphBuilder) NumNodes ¶ added in v1.1.0
func (b *DigraphBuilder) NumNodes() int
NumNodes reports the number of nodes.
func (*DigraphBuilder) OutDegree ¶ added in v1.1.0
func (b *DigraphBuilder) OutDegree(u int) int
OutDegree returns the number of outgoing edges of u. It panics if u is out of range.
func (*DigraphBuilder) RemoveEdge ¶ added in v1.1.0
func (b *DigraphBuilder) RemoveEdge(u, v int) bool
RemoveEdge deletes the directed edge u->v, returning whether it existed. The reverse edge v->u, if present, is unaffected.
func (*DigraphBuilder) Weighted ¶ added in v1.2.0
func (b *DigraphBuilder) Weighted() bool
Weighted reports whether the Builder carries edge weights: always for a NewWeightedDigraphBuilder, and from the first successful DigraphBuilder.AddEdgeW call on for a NewDigraphBuilder.
type Graph ¶
type Graph struct {
// contains filtered or unexported fields
}
Graph is an immutable, undirected graph stored in Compressed Sparse Row form. The neighbors of node u occupy data[offsets[u]:offsets[u+1]] and are sorted ascending; on a weighted graph, weights uses the same indices. A Graph is safe for concurrent reads. The zero value is not a valid Graph; obtain one from Builder.Build.
Accessors that take a node ID (Degree, Neighbors, Weights, EdgeOffset, NeighborsSeq, RandomNeighbor, and the Adjacency aliases) panic with a descriptive message when the ID is outside [0, N); HasEdge, Weight and EdgeIndex take an edge instead and report false for out-of-range endpoints. Slices returned by the accessors are views into the graph's storage with no spare capacity, so appending to one allocates rather than overwriting a neighbor's data.
func (*Graph) EdgeIndex ¶ added in v1.2.0
EdgeIndex returns the slot of the half-edge from u to v in the graph's edge-indexed arrays, that is EdgeOffset(u) plus the position of v in Neighbors(u). ok is false when the edge does not exist or an endpoint is out of range. It runs in O(log deg(u)).
func (*Graph) EdgeOffset ¶ added in v1.2.0
EdgeOffset returns the index at which u's edges start in the graph's edge-indexed arrays: entry i of Neighbors(u), and of Weights(u), sits at slot EdgeOffset(u)+i in an array of length 2*NumEdges, one slot per half-edge. It exists so callers can keep per-edge data of their own in a plain slice laid out exactly like the adjacency and read it in the same loop that walks the neighbors. An undirected edge has a slot at each end; the convention in gonx is that both slots hold the same value. It panics if u is out of range.
Example ¶
package main
import (
"fmt"
"github.com/LuisLSousa/gonx"
)
func main() {
// Per-edge data of your own lives in a slice laid out like the adjacency:
// slot EdgeOffset(u)+i belongs to the edge from u to Neighbors(u)[i].
// Compute it once per half-edge, then read it in the hot loop with no map
// lookup. Here the value is a resistance of 1/(deg(u)+deg(v)).
b := gonx.NewBuilder(4)
b.AddEdge(0, 1)
b.AddEdge(1, 2)
b.AddEdge(1, 3)
g := b.Build()
resistance := make([]float64, 2*g.NumEdges())
for u := range g.Nodes() {
off := g.EdgeOffset(u)
for i, v := range g.Neighbors(u) {
resistance[off+i] = 1 / float64(g.Degree(u)+g.Degree(int(v)))
}
}
// Total resistance around node 1: three edges of 1/(3+1) each.
var total float64
off := g.EdgeOffset(1)
for i := range g.Neighbors(1) {
total += resistance[off+i]
}
fmt.Println(total)
// EdgeIndex finds a single edge's slot without walking the row.
slot, _ := g.EdgeIndex(2, 1)
fmt.Println(resistance[slot])
}
Output: 0.75 0.25
func (*Graph) Edges ¶
Edges iterates over each undirected edge exactly once as (u, v) with u < v.
Example ¶
package main
import (
"fmt"
"github.com/LuisLSousa/gonx"
)
func main() {
b := gonx.NewBuilder(3)
b.AddEdge(0, 1)
b.AddEdge(1, 2)
g := b.Build()
for u, v := range g.Edges() {
fmt.Println(u, v)
}
}
Output: 0 1 1 2
func (*Graph) HasEdge ¶
HasEdge reports whether the undirected edge {u, v} exists. It uses binary search over the smaller-degree endpoint, so it runs in O(log deg).
func (*Graph) HasNegativeWeight ¶ added in v1.2.0
HasNegativeWeight reports whether any edge weighs less than zero. It is always false on an unweighted graph. Build records the answer, so the call is O(1); algorithms that need non-negative weights check it once up front.
func (*Graph) Neighbors ¶
Neighbors returns u's neighbor IDs as a sorted, zero-copy slice into the graph's backing storage. Callers MUST NOT modify the returned slice. It panics if u is out of range.
func (*Graph) NeighborsSeq ¶
NeighborsSeq iterates over u's neighbors in ascending order as ints. It is a convenience wrapper over Graph.Neighbors for callers who want int node IDs end-to-end; hot paths should prefer Neighbors, which exposes the backing slice with no per-element call overhead. It panics if u is out of range.
Example ¶
package main
import (
"fmt"
"github.com/LuisLSousa/gonx"
)
func main() {
b := gonx.NewBuilder(3)
b.AddEdge(0, 2)
b.AddEdge(0, 1)
g := b.Build()
for v := range g.NeighborsSeq(0) {
fmt.Println(v)
}
}
Output: 1 2
func (*Graph) OutNeighbors ¶ added in v1.2.0
OutNeighbors is Graph.Neighbors under the name Adjacency uses. An undirected edge can be walked from either end, so every neighbor is a successor.
func (*Graph) OutWeights ¶ added in v1.2.0
OutWeights is Graph.Weights under the name Adjacency uses.
func (*Graph) RandomNeighbor ¶
RandomNeighbor returns a uniformly random neighbor of u. ok is false when u is isolated. It panics if u is out of range.
func (*Graph) Weight ¶ added in v1.2.0
Weight returns the weight of the edge {u, v}. ok is false when the edge does not exist or an endpoint is out of range. On an unweighted graph every existing edge has weight 1. Like Graph.HasEdge it binary-searches the smaller-degree endpoint's list, so it runs in O(log deg).
func (*Graph) Weighted ¶ added in v1.2.0
Weighted reports whether the graph carries edge weights. An unweighted graph stores no weight array at all: Weights returns nil for every node and Weight reports 1 for every edge.
func (*Graph) Weights ¶ added in v1.2.0
Weights returns the weights of u's edges, aligned index for index with Graph.Neighbors: Weights(u)[i] is the weight of the edge to Neighbors(u)[i]. The slice is zero-copy and MUST NOT be modified. It is nil when the graph is unweighted. It panics if u is out of range.
type Restricted ¶ added in v1.3.0
type Restricted struct {
// contains filtered or unexported fields
}
Restricted is a read-only view of a graph with some nodes and edges hidden, made by RestrictedView. It implements Adjacency, so the traversals that take one, such as metrics.BreadthFirst, metrics.Dijkstra and metrics.ShortestPath, run on it unchanged and see only what is left.
A view shares the graph's storage and copies only the out-lists that lose an entry. Every other node's OutNeighbors and OutWeights are the graph's own slices, returned as they are. Telling the two apart takes one bit test for almost every node, and O(log t) when the test is inconclusive, where t is the number of nodes whose lists changed. A view is immutable and, like the graph, safe to read from several goroutines at once. The zero value is not a valid view.
func RestrictedView ¶ added in v1.3.0
func RestrictedView(g Adjacency, nodes []int, edges [][2]int) *Restricted
RestrictedView returns a view of g with the given nodes and edges hidden, after networkx's restricted_view. Neither g nor the arguments are modified, and the view does not hold on to the argument slices.
Node IDs do not change. A hidden node stays in the view, since Adjacency numbers nodes densely from 0, but it has no edges in either direction: no traversal can enter it, and one that starts there goes nowhere. networkx drops the node instead; distances between the other nodes come out the same.
On an undirected Graph an edge is hidden as a whole, so {u, v} and {v, u} name the same edge and both of its directions disappear. On a Digraph, {u, v} hides only the edge from u to v. Edges between nodes in range that g does not have are ignored, as networkx ignores them; a node out of range, in either argument, panics.
The view keeps the weights of the edges it keeps. Weighted is the same as g's. HasNegativeWeight counts only the edges left, so hiding a graph's only negative edge makes the view usable for Dijkstra.
When g is itself a Restricted view, the result hides the union of both sets over the same underlying graph. g must be a *Graph, a *Digraph or a *Restricted, and any other Adjacency panics: a view is built from the graph's storage, not its methods, so a view of a type that wraps a graph would silently ignore every method the wrapper overrides. Pass the graph it wraps instead.
Building a view takes O(k log k + d) time, where k is the number of edges hidden, counting those taken out with hidden nodes, and d is the combined degree of the nodes whose lists change. A view of a view is built afresh from the union, so hiding edges one at a time through a chain of views costs O(k²) over k steps; pass them to one call instead.
Example ¶
package main
import (
"fmt"
"github.com/LuisLSousa/gonx"
"github.com/LuisLSousa/gonx/metrics"
)
func main() {
// A ring 0-1-2-3 with a short cut from 0 to 2. Hiding the short cut
// answers "what is the best path if this edge were gone?" without
// copying the graph, and without changing it for anyone else reading it.
b := gonx.NewWeightedBuilder(4)
b.AddEdgeW(0, 1, 2)
b.AddEdgeW(1, 2, 2)
b.AddEdgeW(2, 3, 1)
b.AddEdgeW(3, 0, 1)
b.AddEdgeW(0, 2, 1)
g := b.Build()
path, length, _ := metrics.ShortestPath(g, 0, 2)
fmt.Println(path, length)
view := gonx.RestrictedView(g, nil, [][2]int{{0, 2}})
path, length, _ = metrics.ShortestPath(view, 0, 2)
fmt.Println(path, length)
// Hiding node 3 as well leaves the long way round.
path, length, _ = metrics.ShortestPath(gonx.RestrictedView(view, []int{3}, nil), 0, 2)
fmt.Println(path, length)
}
Output: [0 2] 1 [0 3 2] 2 [0 1 2] 4
func (*Restricted) HasNegativeWeight ¶ added in v1.3.0
func (r *Restricted) HasNegativeWeight() bool
HasNegativeWeight reports whether any edge left visible weighs less than zero. RestrictedView records the answer, so the call is O(1).
func (*Restricted) NumNodes ¶ added in v1.3.0
func (r *Restricted) NumNodes() int
NumNodes reports the number of nodes, hidden ones included.
func (*Restricted) OutNeighbors ¶ added in v1.3.0
func (r *Restricted) OutNeighbors(u int) []int32
OutNeighbors returns the targets of u's visible outgoing edges, ascending: the underlying graph's own slice when the view changes nothing at u, and otherwise a filtered copy the view holds. It is empty for a hidden node. Callers MUST NOT modify the returned slice. It panics if u is out of range.
func (*Restricted) OutWeights ¶ added in v1.3.0
func (r *Restricted) OutWeights(u int) []float64
OutWeights returns the weights of u's visible outgoing edges, aligned with Restricted.OutNeighbors, or nil when the graph is unweighted. Callers MUST NOT modify the returned slice. It panics if u is out of range.
func (*Restricted) Weighted ¶ added in v1.3.0
func (r *Restricted) Weighted() bool
Weighted reports whether the underlying graph carries edge weights.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
communities
command
Renders a planted-partition graph for the README gallery: four dense communities joined by sparse bridges, built directly with gonx.Builder and colored by community.
|
Renders a planted-partition graph for the README gallery: four dense communities joined by sparse bridges, built directly with gonx.Builder and colored by community. |
|
internal/render
Package render lays out gonx graphs and writes them as self-contained SVG images.
|
Package render lays out gonx graphs and writes them as self-contained SVG images. |
|
karate
command
Renders Zachary's Karate Club — the classic 34-node social network from Zachary (1977) — for the README gallery.
|
Renders Zachary's Karate Club — the classic 34-node social network from Zachary (1977) — for the README gallery. |
|
scalefree
command
Renders a Barabási–Albert scale-free network for the README gallery.
|
Renders a Barabási–Albert scale-free network for the README gallery. |
|
smallworld
command
Renders a Watts–Strogatz small-world network for the README gallery.
|
Renders a Watts–Strogatz small-world network for the README gallery. |
|
Package generators builds graphs from classic random-graph models.
|
Package generators builds graphs from classic random-graph models. |
|
internal
|
|
|
pool
Package pool provides a minimal static work-partitioning helper used by the parallel graph metrics.
|
Package pool provides a minimal static work-partitioning helper used by the parallel graph metrics. |
|
Package metrics computes structural graph properties: clustering, connectivity, and shortest-path-based measures.
|
Package metrics computes structural graph properties: clustering, connectivity, and shortest-path-based measures. |
|
Package transform contains structure-preserving and structure-randomizing graph transformations: copying, relabeling, and degree-preserving edge swaps.
|
Package transform contains structure-preserving and structure-randomizing graph transformations: copying, relabeling, and degree-preserving edge swaps. |