graph

package
v0.1.0-rc.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNilNode is returned when a nil node is passed to a graph mutation.
	ErrNilNode = errors.New("graph: node is nil")
	// ErrNodeAlreadyExists is returned when adding a node whose key is already
	// present in the graph.
	ErrNodeAlreadyExists = errors.New("graph: node already exists")
	// ErrNodeNotFound is returned when an operation references a node that is
	// not in the graph.
	ErrNodeNotFound = errors.New("graph: node not found")
	// ErrSourceNotFound is returned by traversal algorithms (e.g. BFS) when the
	// requested source vertex is absent from the graph.
	ErrSourceNotFound = errors.New("algorithms: source vertex not found in graph")
	// ErrEmptyGraph is returned by ranking algorithms (e.g. PageRank) when the
	// graph has no edges to rank over.
	ErrEmptyGraph = errors.New("algorithms: graph has no edges to rank")
)

Functions

This section is empty.

Types

type Algorithm

type Algorithm[K comparable, P float32 | float64] interface {
	Run(g Graph[K, P]) (AlgorithmResult, error)
}

Algorithm is the common root of every graph algorithm. It exposes only identity; the runnable contract lives on the Traversal and Ranking sub-interfaces, which differ in what they consume and produce.

type AlgorithmResult

type AlgorithmResult interface {
}

type BFS

type BFS[K comparable, P float32 | float64] struct {
	// contains filtered or unexported fields
}

BFS is a breadth-first traversal: it explores the graph frontier by frontier (nearest vertices first) using a FIFO queue. It implements Traversal; Run starts from the source configured at construction, while Traverse takes the source as a parameter so one BFS value can serve many seeds.

func NewBFS

func NewBFS[K comparable, P float32 | float64](source K, dir Direction) *BFS[K, P]

NewBFS returns a breadth-first traversal that starts from source and follows edges in the given direction.

func NewBFSTraversal

func NewBFSTraversal[K comparable, P float32 | float64](dir Direction) *BFS[K, P]

NewBFSTraversal returns a breadth-first Traversal following edges in the given direction, for use as a graph's configured search traversal (where the source is supplied per Traverse call).

func (*BFS[K, P]) Clone

func (b *BFS[K, P]) Clone() Traversal[K, P]

Clone returns a new BFS with the same direction and no source set, so it can be given its own source and run independently of the original. This lets a single configured traversal serve many concurrent walks without sharing the mutable source.

func (*BFS[K, P]) Run

func (b *BFS[K, P]) Run(g Graph[K, P]) (AlgorithmResult, error)

Run traverses g from the configured source and returns the result as an AlgorithmResult (a TraversalResult).

func (*BFS[K, P]) SetSource

func (b *BFS[K, P]) SetSource(source K)

sets source. Useful to change source and run multiple traversals with the same object

type Direction

type Direction int

Direction selects which edges a traversal follows in a directed graph.

const (
	// Outgoing follows edges from a vertex to its successors (AdjacencyMap).
	Outgoing Direction = iota

	// Incoming follows edges from a vertex to its predecessors (PredecessorMap).
	Incoming

	// Both treats edges as undirected, following successors and predecessors.
	Both
)

type Entity

type Entity[K comparable] interface {
	Node[K]

	GetValue() string
}

type ExcessTraversal

type ExcessTraversal[K cmp.Ordered, P float32 | float64] struct {
	// contains filtered or unexported fields
}

ExcessTraversal is the excess methodology's traversal: from a source fact it visits the source's anchors — Topic and NamedEntity neighbours, both edge directions — at depth 1, then every member of those anchors at depth 2. Order lists the anchors then the members, ascending key within each band, so the collection layer's float folds run in a fixed order. Parents carries the full member→anchors incidence (a member reached through two of the source's anchors is two observations, not one), and the source itself appears among its anchors' members: observed anchor mass then aggregates identically for every observer, and self-exclusion happens once, in the scorer, where the member's own mass is at hand. K is ordered, not merely comparable: the band ordering is what lets the collection layer fold floats without run-to-run drift, and an unordered key type would have no order to band by.

func NewExcessTraversal

func NewExcessTraversal[K cmp.Ordered, P float32 | float64]() *ExcessTraversal[K, P]

NewExcessTraversal returns the excess-transmission traversal.

func (*ExcessTraversal[K, P]) Clone

func (t *ExcessTraversal[K, P]) Clone() Traversal[K, P]

Clone returns a fresh traversal with no per-run state, so each walk can set its own source instead of mutating a shared instance.

func (*ExcessTraversal[K, P]) Run

func (t *ExcessTraversal[K, P]) Run(g Graph[K, P]) (AlgorithmResult, error)

Run traverses from the configured source, returning an AlgorithmResult (a TraversalResult).

func (*ExcessTraversal[K, P]) SetSource

func (t *ExcessTraversal[K, P]) SetSource(source K)

SetSource configures the vertex the next Run starts from.

type Fact

type Fact[K comparable] struct {
	NodeAttributes
	Hasher hash.Hasher[K, string] `json:"-"`
}

func (Fact[K]) GetAttributes

func (f Fact[K]) GetAttributes() *NodeAttributes

func (Fact[K]) GetTimestamp

func (f Fact[K]) GetTimestamp() time.Time

func (Fact[K]) GetValue

func (f Fact[K]) GetValue() string

func (Fact[K]) Hash

func (f Fact[K]) Hash(h hash.Hasher[K, string]) K

Hash keys the fact by its text in the fact namespace, so a topic or entity reading the same text is a different node (see Node).

func (Fact[K]) Key

func (f Fact[K]) Key() K

type Graph

type Graph[K comparable, P float32 | float64] interface {
	GetHasher() hash.Hasher[K, string]

	// Get returns the node stored under key, or nil if absent.
	Get(key K) Node[K]

	// Set inserts a new node, deriving its key via the graph's hash
	// function.
	Set(node Node[K]) error

	// Put replaces the node stored under key with the given node.
	Put(key K, node Node[K]) error

	// Delete removes the node (and, by extension, its index entries and
	// incident relationships).
	Delete(node Node[K]) error

	// GetVectorIndex returns the graph's vector (semantic) index, keyed
	// by node key and storing embedding vectors of precision P.
	GetVectorIndex() index.VectorIndex[K, P]

	// GetTextIndex returns the graph's full-text search index.
	GetTextIndex() index.TextIndex[K, P]

	// MergeFrom merges the contents of g into this graph: nodes,
	// relationships and index entries. Nodes with colliding keys are
	// resolved by the implementation.
	MergeFrom(g Graph[K, P])

	// Copy returns a deep copy of the graph, independent of the
	// original: mutating one never affects the other.
	Copy() Graph[K, P]

	Nodes() map[K]Node[K]

	// AdjacencyMap returns the outgoing-edge view of the graph:
	// AdjacencyMap()[from][to] is the relationship from -> to.
	AdjacencyMap() map[K]map[K]K

	// PredecessorMap returns the incoming-edge view of the graph:
	// PredecessorMap()[to][from] is the relationship from -> to. It is
	// the transpose of AdjacencyMap and serves reverse traversal.
	PredecessorMap() map[K]map[K]K

	// Neighbours returns the keys adjacent to key in either direction
	// (successors and predecessors), in unspecified order. Unlike
	// AdjacencyMap/PredecessorMap it copies no more than one node's edge
	// rows, so a source-rooted traversal can read a single node's
	// neighbourhood without cloning the entire edge set on every call.
	Neighbours(key K) []K

	// Order returns the number of entities (vertices) in the graph.
	Order() int

	// Size returns the number of relationships (edges) in the graph.
	Size() int

	// Stats returns a point-in-time snapshot of the graph's shape.
	Stats() GraphStats

	// Search runs a hybrid query over the graph and returns matching
	// nodes alongside their ranking scores and the contribution records
	// the scores were folded from (parallel slices, ordered best-first,
	// at most top entries), plus the query's background rate — the one
	// query-global observation the scoring fold used, which explain
	// serializes so a client can recompute every score from its payload.
	// A caller that only wants ranked hits discards both.
	//
	// All criteria are optional and combine to narrow the result:
	//   - keywords: full-text terms matched against the text index
	//   - vector:   query embedding for nearest-neighbor search; nil
	//               (or empty) skips the vector index
	//   - topics:   restrict results to facts tagged with these topics
	//   - entities: restrict results to facts involving these entities
	//   - depth:    selects the retrieval lane. 0 skips the anchor
	//               traversal and ranks by seed mass alone (the floor; the
	//               fast, text-only lane). 1 and 2 both run the one
	//               anchor-mediated round and differ only in how much
	//               above-chance evidence an anchor needs to transmit: 1 is
	//               the precision lane, 2 admits at the plain fair share for
	//               maximum recall. It does not iterate
	//   - top:      maximum number of results returned
	//   - since:    inclusive lower time bound; zero value = unbounded
	//   - until:    exclusive upper time bound; zero value = unbounded
	Search(keywords []string, vector containers.Vector[K, P], topics []string, entities []string, depth int, top int, since time.Time, until time.Time) ([]*Node[K], []P, [][]scoring.Contribution[K, P], P)

	// RLock acquires the lock for reading.
	RLock()

	// Lock acquires the lock for writing.
	Lock()

	// RUnlock releases a read lock.
	RUnlock()

	// Unlock releases a write lock.
	Unlock()
}

Graph is a temporal memory graph: the storage atomic component of the database server. A la Redis, every database server holds multiple in-memory graphs addressed by index (see the '@n' graph selector in FQL).

K is the node key type; P is the floating-point precision used for embedding vectors and ranking scores.

Implementations are guarded by the embedded read-write lock methods; callers are responsible for acquiring the appropriate lock around the operations they compose (see the locking section below).

type GraphStats

type GraphStats struct {
	Order   int `json:"order"`   // number of entities (vertices)
	Size    int `json:"size"`    // number of relationships (edges)
	Nodes   int `json:"nodes"`   // total stored nodes
	Vectors int `json:"vectors"` // total vectors indexed
	// ForestEntries is how many entries the vector forest holds (live vectors
	// plus garbage awaiting compaction); bounded by the index's flush factor
	// times Vectors. 0 for index implementations without a forest.
	ForestEntries int `json:"forest_entries"`
}

GraphStats is a point-in-time snapshot of a graph's shape.

type InMemoryGraph

type InMemoryGraph[K ~uint64, P float32 | float64] struct {
	// contains filtered or unexported fields
}

InMemoryGraph is the in-process implementation of Graph. Nodes live in a key-addressed map, relationships in a pair of mirrored adjacency maps, and two secondary indices serve hybrid search: a BTree full-text index over the nodes' values and an RPTree (random projection forest) vector index over caller-provided embeddings.

func NewGraph

func NewGraph[K ~uint64, P float32 | float64](cfg *config.ConfigSet) *InMemoryGraph[K, P]

func (*InMemoryGraph[K, P]) AdjacencyMap

func (g *InMemoryGraph[K, P]) AdjacencyMap() map[K]map[K]K

func (*InMemoryGraph[K, P]) Copy

func (g *InMemoryGraph[K, P]) Copy() Graph[K, P]

Copy returns a deep copy of the graph: nodes, relationships and both indices are rebuilt so mutating one graph never affects the other.

func (*InMemoryGraph[K, P]) Delete

func (g *InMemoryGraph[K, P]) Delete(node Node[K]) error

Delete removes the node, its incident relationships and its index entries. Whichever end of an edge is deleted, the edge leaves as a whole — its node and both adjacency entries — because the two halves are one fact about the graph: a Mentions left in idToNodes describes an edge that no longer exists (Nodes and Stats keep reporting it, and it keeps its text-index entry), while an adjacency entry left behind names a relationship node that is no longer stored, so Size counts an edge AdjacencyMap cannot resolve.

func (*InMemoryGraph[K, P]) Get

func (g *InMemoryGraph[K, P]) Get(key K) Node[K]

Get returns the node stored under key, or nil if absent.

func (*InMemoryGraph[K, P]) GetHasher

func (g *InMemoryGraph[K, P]) GetHasher() hash.Hasher[K, string]

Get hasher

func (*InMemoryGraph[K, P]) GetTextIndex

func (g *InMemoryGraph[K, P]) GetTextIndex() index.TextIndex[K, P]

Returns the graph full text search index

func (*InMemoryGraph[K, P]) GetVectorIndex

func (g *InMemoryGraph[K, P]) GetVectorIndex() index.VectorIndex[K, P]

Returns the graph vector index

func (*InMemoryGraph[K, P]) Lock

func (g *InMemoryGraph[K, P]) Lock()

write lock

func (*InMemoryGraph[K, P]) MergeFrom

func (g *InMemoryGraph[K, P]) MergeFrom(in Graph[K, P])

MergeFrom merges the contents of in into this graph: nodes, relationships and index entries. On key collision the incoming node wins.

func (*InMemoryGraph[K, P]) Neighbours

func (g *InMemoryGraph[K, P]) Neighbours(key K) []K

Neighbours returns the keys adjacent to key in either direction without copying the whole edge set: it allocates one slice sized to that node's own degree. This is the read a source-rooted traversal needs — the per-seed ExcessTraversal uses it instead of AdjacencyMap/PredecessorMap, which each deep-copy every edge in the graph on every call.

func (*InMemoryGraph[K, P]) Nodes

func (g *InMemoryGraph[K, P]) Nodes() map[K]Node[K]

func (*InMemoryGraph[K, P]) Order

func (g *InMemoryGraph[K, P]) Order() int

Order returns the number of entities (vertices) in the graph.

func (*InMemoryGraph[K, P]) PredecessorMap

func (g *InMemoryGraph[K, P]) PredecessorMap() map[K]map[K]K

func (*InMemoryGraph[K, P]) Put

func (g *InMemoryGraph[K, P]) Put(key K, node Node[K]) error

Put stores node under key, replacing whatever was there.

func (*InMemoryGraph[K, P]) RLock

func (g *InMemoryGraph[K, P]) RLock()

read lock

func (*InMemoryGraph[K, P]) RUnlock

func (g *InMemoryGraph[K, P]) RUnlock()

read unlock

func (*InMemoryGraph[K, P]) Search

func (g *InMemoryGraph[K, P]) Search(keywords []string, vector containers.Vector[K, P], topics []string, entities []string, depth int, top int, since time.Time, until time.Time) ([]*Node[K], []P, [][]scoring.Contribution[K, P], P)

func (*InMemoryGraph[K, P]) Set

func (g *InMemoryGraph[K, P]) Set(node Node[K]) error

Set inserts a new node under its own ID, returning ErrNodeAlreadyExists if the key is taken.

func (*InMemoryGraph[K, P]) SetRanking

func (g *InMemoryGraph[K, P]) SetRanking(r Ranking[K, P])

SetRanking installs the global ranking algorithm Search boosts scores with (typically an algorithms.Ranking such as PageRank).

func (*InMemoryGraph[K, P]) SetScorer

func (g *InMemoryGraph[K, P]) SetScorer(s scoring.Scorer[K, P])

SetScorer installs the scorer Search folds candidate contributions with. nil is ignored rather than stored: unlike its peers, whose nil means "use the fallback behaviour", a graph without a scorer cannot rank at all.

func (*InMemoryGraph[K, P]) SetTraversal

func (g *InMemoryGraph[K, P]) SetTraversal(t Traversal[K, P])

SetTraversal installs the traversal algorithm Search expands seeds with (typically an algorithms.Traversal such as BFS).

func (*InMemoryGraph[K, P]) Size

func (g *InMemoryGraph[K, P]) Size() int

Size returns the number of relationships (edges) in the graph.

func (*InMemoryGraph[K, P]) Stats

func (g *InMemoryGraph[K, P]) Stats() GraphStats

func (*InMemoryGraph[K, P]) Unlock

func (g *InMemoryGraph[K, P]) Unlock()

write unlock

type IsAbout

type IsAbout[K comparable] struct {
	Fact  *Fact[K]
	Topic *Topic[K]
	NodeAttributes

	Hasher hash.Hasher[K, string]
}

Fact is about Topic relationship

func (IsAbout[K]) GetAttributes

func (a IsAbout[K]) GetAttributes() *NodeAttributes

func (IsAbout[K]) GetTimestamp

func (a IsAbout[K]) GetTimestamp() time.Time

func (IsAbout[K]) GetValue

func (a IsAbout[K]) GetValue() string

func (IsAbout[K]) Hash

func (a IsAbout[K]) Hash(h hash.Hasher[K, string]) K

Hash identifies the edge by the (fact, topic) pair it connects. Hashing the (empty) attribute value instead would collapse every IsAbout edge onto one key, so Set would keep only the first and drop the rest.

func (IsAbout[K]) Key

func (a IsAbout[K]) Key() K

func (IsAbout[K]) Source

func (a IsAbout[K]) Source() *Entity[K]

func (IsAbout[K]) Target

func (a IsAbout[K]) Target() *Entity[K]

type Mentions

type Mentions[K comparable] struct {
	Fact        *Fact[K]
	NamedEntity *NamedEntity[K]
	NodeAttributes

	Hasher hash.Hasher[K, string]
}

Fact mentions NamedEntity relationship

func (Mentions[K]) GetAttributes

func (m Mentions[K]) GetAttributes() *NodeAttributes

func (Mentions[K]) GetTimestamp

func (m Mentions[K]) GetTimestamp() time.Time

func (Mentions[K]) GetValue

func (m Mentions[K]) GetValue() string

func (Mentions[K]) Hash

func (m Mentions[K]) Hash(h hash.Hasher[K, string]) K

Hash identifies the edge by the (fact, entity) pair it connects. Hashing the (empty) attribute value instead would collapse every Mentions edge onto one key, so Set would keep only the first and drop the rest.

func (Mentions[K]) Key

func (m Mentions[K]) Key() K

func (Mentions[K]) Source

func (m Mentions[K]) Source() *Entity[K]

func (Mentions[K]) Target

func (m Mentions[K]) Target() *Entity[K]

type NamedEntity

type NamedEntity[K comparable] struct {
	NodeAttributes

	Hasher hash.Hasher[K, string] `json:"-"`
}

func (*NamedEntity[K]) GetAttributes

func (n *NamedEntity[K]) GetAttributes() *NodeAttributes

func (NamedEntity[K]) GetTimestamp

func (n NamedEntity[K]) GetTimestamp() time.Time

func (NamedEntity[K]) GetValue

func (n NamedEntity[K]) GetValue() string

func (NamedEntity[K]) Hash

func (n NamedEntity[K]) Hash(h hash.Hasher[K, string]) K

Hash keys the entity by its name in the entity namespace, so a fact whose whole text is that name is a different node (see Node).

func (NamedEntity[K]) Key

func (n NamedEntity[K]) Key() K

type Node

type Node[K comparable] interface {
	hash.Hashable[K, string]

	Key() K
	GetValue() string
	GetTimestamp() time.Time
	GetAttributes() *NodeAttributes
}

Node is anything the graph stores under a key: the entities (facts, topics, named entities) and the relationships between them.

A node's key is the hash of its own type tag followed by its material — "fact:", "topic:", "entity:", "mentions:", "isabout:" — so identity is (type, value), never value alone: a fact and a topic that read the same are two different nodes. Without the tag, `remember 'billing' topic:billing` hashes both to one key, and then the topic node is never stored (Set finds the fact already there) while its IsAbout edge points from the fact back to itself. A new participant takes a tag that no other one prefixes; the five above differ in their first byte.

type NodeAttributes

type NodeAttributes struct {
	Value     string
	Timestamp time.Time
}

type PageRank

type PageRank[K comparable, P float32 | float64] struct {
	// contains filtered or unexported fields
}

PageRank scores every vertex by the stationary distribution of a random walk that follows an outgoing edge with probability damping and teleports to a uniformly random vertex with probability 1-damping. It iterates until the scores change by less than tol or maxIter iterations are reached. It implements Ranking.

func NewPageRank

func NewPageRank[K comparable, P float32 | float64](damping P, maxIter int, tol P) *PageRank[K, P]

NewPageRank returns a PageRank ranking with the given damping factor, iteration cap and convergence tolerance.

func (*PageRank[K, P]) Name

func (pr *PageRank[K, P]) Name() string

Name returns the algorithm identifier.

func (*PageRank[K, P]) Run

func (pr *PageRank[K, P]) Run(g Graph[K, P]) (AlgorithmResult, error)

Run ranks every vertex of g and returns the result as an AlgorithmResult (a RankingResult).

type Ranking

type Ranking[K comparable, P float32 | float64] interface {
	Algorithm[K, P]
	// contains filtered or unexported methods
}

Ranking assigns a score to every vertex from the graph's global structure, rather than from a single source (PageRank and other centralities).

type RankingResult

type RankingResult[K comparable, P float32 | float64] struct {
	AlgorithmResult

	// Scores maps each vertex to its computed score.
	Scores map[K]P
}

RankingResult captures the outcome of a whole-graph ranking.

type Relationship

type Relationship[K comparable] interface {
	Node[K]

	Source() *Entity[K]
	Target() *Entity[K]
}

type Topic

type Topic[K comparable] struct {
	ID K
	NodeAttributes

	Hasher hash.Hasher[K, string] `json:"-"`
}

func (*Topic[K]) GetAttributes

func (t *Topic[K]) GetAttributes() *NodeAttributes

func (Topic[K]) GetTimestamp

func (t Topic[K]) GetTimestamp() time.Time

func (Topic[K]) GetValue

func (t Topic[K]) GetValue() string

func (Topic[K]) Hash

func (t Topic[K]) Hash(h hash.Hasher[K, string]) K

Hash keys the topic by its name in the topic namespace, so a fact whose whole text is that name is a different node (see Node).

func (Topic[K]) Key

func (t Topic[K]) Key() K

type Traversal

type Traversal[K comparable, P float32 | float64] interface {
	Algorithm[K, P]

	// Sets the traversal source
	SetSource(source K)

	// Clone returns a fresh traversal with the same configuration but no
	// per-run state, so each walk can set its own source and run on its own
	// instance instead of mutating a shared one.
	Clone() Traversal[K, P]
	// contains filtered or unexported methods
}

Traversal explores a graph starting from a source vertex, visiting reachable vertices in an order defined by the concrete algorithm (breadth-first for BFS, depth-first for DFS). K is the vertex key type and P the graph's score precision.

type TraversalResult

type TraversalResult[K comparable] struct {
	AlgorithmResult
	// Order lists the vertices in the order they were first visited.
	Order []K

	// Parent maps each visited vertex to the vertex it was discovered from,
	// forming the traversal tree. The source maps to its own key.
	Parent map[K]K

	// Parents maps each visited vertex to every vertex it was reached through
	// at the previous depth. Parent keeps the canonical (first-in-Order) entry
	// for tree-shaped consumers; Parents carries the full incidence, because a
	// member reached through two of the source's anchors is two observations,
	// not one. Tree-shaped traversals may leave it nil.
	Parents map[K][]K

	// Depth maps each visited vertex to its hop distance from the source.
	Depth map[K]int
}

TraversalResult captures the outcome of a source-based traversal.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL