mnestic
GitHub

Utilities & algorithms

Fixed rules are the algorithms and utilities you apply with <~ instead of :=. They cover what pure Datalog cannot express well — ranking within a group, shortest paths with output paths, community detection — and their results are ordinary relations, so you can join, filter, and aggregate them in the same query. This page is the complete catalog: for each rule, the input relation shape, the options with their defaults, and the output columns.

The graph algorithms require the graph-algo feature. It is part of the default compact feature set, so the crates.io crate built with default features, the cozo-bin server/REPL, and the PyPI mnestic wheel all include it. The utilities are available in every build. You can also register custom fixed rules from the host language: SimpleFixedRule / Db::register_fixed_rule in Rust, and register_fixed_rule on the Python client.

Examples on this page run against a small agent-memory graph: a memory relation (id => kind, text, importance, at, v) holding eight episodic memories m1m8, and a weighted association relation recalls { from, to => strength }:

m1 ─0.2→ m2 ─0.6→ m3 ─0.9→ m4 ─0.85→ m5
          └────────── 0.4 ──────────→ m5
m6 ─0.5→ m7 ─0.7→ m8 ─────── 0.3 ───→ m5

Two chains of memories converge on m5, the decision to cap SST file sizes. Which memory anchors this web? One fixed rule, one Datalog rule to join the scores back onto the text:

ranks[m, r] <~ PageRank(*recalls[])
?[text, score] := ranks[m, r], *memory{id: m, text}, score = round(r * 1000) / 1000
:order -score
:limit 3
text                                                        score
'Cap SST file size at 128 MB'                               0.115
'Slow connector queries trace to a missing tenant_id index' 0.048
'Compaction stalls correlate with oversized SST files'      0.047

How edge relations are read

Most algorithms take an edge relation as their first input: column 0 is the source node, column 1 the target, and an optional column 2 the weight. Columns beyond those are ignored. A missing weight column means every edge weighs 1.0; weights must be finite numbers, and the pathfinding and centrality algorithms additionally require them to be non-negative. A violation is an algo::invalid_edge_weight error everywhere except ShortestPathAStar, which assumes non-negativity but checks only for NaN (see its section). Nodes can be values of any type, compared by the same identity joins use (so 1 and 1.0 are distinct nodes). The exceptions are BFS, DFS, ShortestPathBFS, ShortestPathAStar and RandomWalk, which walk the edge relation directly and can evaluate expressions against its full rows.

mnestic

Two hardening changes landed in mnestic 0.11.0. An empty edge relation now returns no rows from TopSort, ConnectedComponents, StronglyConnectedComponents, ClusteringCoefficients, BetweennessCentrality, ClosenessCentrality and LabelPropagation — in upstream CozoDB it panicked and aborted the process (PageRank already returned no rows). And a graph too large for the internal 32-bit adjacency (2³² or more vertices, or that many directed edge slots) is now a loud algo::graph_too_large error instead of silently wrapping. Graph builds also honor ::kill and :timeout.

Reusing a graph across queries (mnestic 0.11.0+)

Every algorithm below that takes an edges relation rebuilds its internal adjacency structure from scratch on each call. If you run several algorithms over the same graph, or the same algorithm repeatedly, name the graph once with ::graph create and pass it with the graph: option instead of the positional edge relation:

::graph create assoc { edges: recalls }
?[memory, rank] <~ PageRank(graph: 'assoc')

The projection is always fresh — writing to a source relation invalidates it — and it is kept in memory only, so re-create it after a restart (using a stale name is a loud error). See Graph projections for the freshness protocol, the memory ceiling, and measured speedups.

Supported by ConnectedComponents, StronglyConnectedComponents (SCC), PageRank, ClusteringCoefficients, TopSort, BetweennessCentrality, ClosenessCentrality, ShortestPathDijkstra, KShortestPathYen, MinimumSpanningTreePrim, MinimumSpanningForestKruskal, LabelPropagation, CommunityDetectionLouvain and, since mnestic 0.12.0, BudgetedTraversal. When graph: is given, the remaining positional inputs shift down by one.

BFS, DFS, ShortestPathBFS, RandomWalk, ShortestPathAStar and DegreeCentrality cannot use a projection: they evaluate per-tuple condition, heuristic and weight expressions against the edge relation, which a compressed adjacency does not carry. Passing graph: to them is a parse error.

Utilities

Constant(...)

Constant(data: [...])

Returns a relation containing the data passed in. The constant rule ?[] <- ... is syntax sugar for ?[] <~ Constant(data: ...).

  • data: A list of lists, representing the rows of the returned relation. Every row must have the same length, and the rule head (if given) must match that arity. An empty data with no explicit rule head is an error.

ReorderSort(...)

ReorderSort(rel[...], out: [...], sort_by: [...], descending: false, break_ties: false, skip: 0, take: 0)

Sort and then extract new columns of the passed in relation rel.

  • out (required): A list of expressions which will be used to produce the output relation. Any bindings in the expressions will be bound to the named positions in rel.
  • sort_by: A list of expressions which will be used to produce the sort keys. Any bindings in the expressions will be bound to the named positions in rel.
  • descending: Whether the sorting process should be done in descending order. Defaults to false.
  • break_ties: Whether ties should be broken, e.g. whether the first two rows with identical sort keys should be given ordering numbers 1 and 2 instead of 1 and 1. Defaults to false (a run of ties shares one number, and the next distinct value resumes from the true position: 1, 1, 3).
  • skip: How many rows to skip before producing rows. Defaults to zero.
  • take: How many rows at most to produce. Zero means no limit. Defaults to zero.
  • Returns: The returned relation, in addition to the rows specified in the parameter out, will have the ordering prepended. The ordering starts at 1.
?[ord, text] <~ ReorderSort(*memory[id, kind, text, importance, at, v],
                            out: [text], sort_by: [importance],
                            descending: true, take: 2)
ord  text
1    'Chose RocksDB over sled for the write path'
2    'Cap SST file size at 128 MB'

Note

This utility serves a similar purpose to the global :order, :limit and :offset options, but can be applied to intermediate results. Prefer the global options if you are ordering the final output.

CsvReader(...)

CsvReader(url: ..., types: [...], delimiter: ',', prepend_index: false, has_headers: true)

Read a CSV file from disk or an HTTP GET request and convert the result to a relation.

Reading from http:// or https:// URLs requires the requests feature (part of the default compact set); without it, only local files work. This utility is not available on WASM targets (which mnestic does not currently ship).

  • url (required): URL for the CSV file. For local file, use file://<PATH_TO_FILE>.
  • types (required): A list of strings interpreted as types for the columns of the output relation. If any type is specified as nullable and conversion to the specified type fails, null will be the result. This is more lenient than other functions since CSVs tend to contain lots of bad values.
  • delimiter: The delimiter to use when parsing the CSV file. Must be a single-byte string.
  • prepend_index: If true, row index will be prepended to the columns.
  • has_headers: Whether the CSV file has headers. The reader will not interpret the header in any way but will instead ignore it.

JsonReader(...)

JsonReader(url: ..., fields: [...], json_lines: true, null_if_absent: false, prepend_index: false)

Read a JSON file from disk or an HTTP GET request and convert the result to a relation. The same feature and platform restrictions as CsvReader apply.

  • url (required): URL for the JSON file. For local file, use file://<PATH_TO_FILE>.
  • fields (required): A list of field names, for extracting fields from JSON objects into the relation.
  • json_lines: If true, parse the file as lines of JSON objects, each line containing a single object; if false, parse the file as a JSON array containing many objects.
  • null_if_absent: If true and a requested field is absent, will output null in its place. If false and the requested field is absent, will throw an error.
  • prepend_index: If true, row index will be prepended to the columns.

ReciprocalRankFusion(...)

ReciprocalRankFusion(lists[list_id, item, score], k: 60, descending: true, detailed: false)

mnestic

Specific to mnestic — ReciprocalRankFusion and MaximalMarginalRelevance landed in 0.8.0; the detailed: long format in 0.8.4. They are the Datalog primitives beneath the one-call hybrid_search API — see Hybrid retrieval.

Fuses several ranked result lists (for example a vector leg, a full-text leg, and a graph leg) into one ranking: each item scores Σ 1/(k + rank_in_list) over the lists it appears in. Datalog can sum reciprocal contributions on its own, but it cannot assign a rank position within a group — that intra-list ranking is what this rule supplies.

  • Input: exactly three columns [list_id, item, score]. Rows are grouped by list_id and ranked within each list by score. An item appearing more than once in a list keeps its best score. A non-finite score (NaN or ±inf) is an error.
  • k: The rank-bias damping constant. Defaults to 60; values below 0 are clamped to 0.
  • descending: Whether a higher score is better within a list. Defaults to true. Mixed-direction lists (e.g. distances and relevances) should be normalised by the caller, or fused in separate invocations.
  • detailed: Defaults to false. Must be a constant boolean, since it changes the output arity.
  • Returns: Pairs [item, fused_score]. With detailed: true, instead one row per (item, contributing list): [item, fused_score, list_id, leg_rank, leg_score], from which the fused score reconstructs exactly.
candidates[list, item, score] <- [
    ['semantic', 'm4', 0.95], ['semantic', 'm3', 0.85], ['semantic', 'm5', 0.70],
    ['text',     'm4', 8.1],  ['text',     'm3', 6.3],  ['text',     'm7', 2.2],
]
fused[item, s] <~ ReciprocalRankFusion(candidates[], k: 60)
?[item, score] := fused[item, s], score = round(s * 10000) / 10000
:order -score
item   score
'm4'   0.0328
'm3'   0.0323
'm5'   0.0159
'm7'   0.0159

m4 ranks first in both lists and m3 second in both; m5 and m7 each appear in only one list, so they tie on a single third-rank contribution.

RRF(...)

RRF(...)

See ReciprocalRankFusion.

MaximalMarginalRelevance(...)

MaximalMarginalRelevance(candidates[item, relevance, vector], lambda: 0.5, k: 0)

mnestic

Specific to mnestic (0.8.0). See Hybrid retrieval for how it composes with ReciprocalRankFusion and the one-call API.

Re-ranks a candidate set to balance relevance against diversity, so a result list is not dominated by near-duplicates. At each step it greedily selects the candidate maximising λ·relevance − (1−λ)·max cosine_sim to the already-selected set. The diversity term uses the true maximum cosine similarity, so anti-correlated candidates earn diversity credit rather than being clamped at zero.

  • Input: exactly three columns [item, relevance, vector]. relevance must be a finite number and vector a vector value; all vectors must share one dimension (a mismatch is an error, not a crash).
  • lambda: 1 is pure relevance, 0 pure diversity. Defaults to 0.5, clamped to [0, 1].
  • k: How many candidates to select. Defaults to 0, meaning all.
  • Returns: Pairs [item, rank] in selection order, rank starting at 1.

Here the compaction memories m3, m4, m5 embed close together while m7 (the connector timeout) sits elsewhere. Pure relevance would return the three near-duplicates first; MMR lets m7 jump the queue:

cands[item, relevance, v] := *memory{id: item, importance: relevance, v},
                             is_in(item, ['m3', 'm4', 'm5', 'm7'])
?[item, rank] <~ MaximalMarginalRelevance(cands[], lambda: 0.5, k: 3)
:order rank
item   rank
'm5'   1
'm7'   2
'm4'   3

MMR(...)

MMR(...)

See MaximalMarginalRelevance.

Connectedness algorithms

ConnectedComponents(...)

ConnectedComponents(edges[from, to], nodes?[idx])

Computes the connected components of a graph with the provided edges, treating them as undirected (weak connectivity).

  • nodes: An optional second relation naming vertices. A vertex appearing here but in no edge gets a singleton component of its own; without it the vertex set is exactly the set of edge endpoints.
  • Returns: Pairs containing the node, and its component index. The component indices themselves are arbitrary labels.

The two recalls chains meet at m5, so the whole graph is one component:

cc[memory, comp] <~ ConnectedComponents(*recalls[])
?[comp, count(memory)] := cc[memory, comp]
comp  count(memory)
0     8

StronglyConnectedComponents(...)

StronglyConnectedComponents(edges[from, to], nodes?[idx])

Computes the strongly connected components of a graph with the provided edges, interpreted as directed. Nodes on no directed cycle form singleton components. The optional nodes relation works as in ConnectedComponents.

  • Returns: Pairs containing the node, and its component index.

SCC(...)

SCC(...)

See StronglyConnectedComponents.

MinimumSpanningForestKruskal(...)

MinimumSpanningForestKruskal(edges[from, to, weight?])

Runs Kruskal's algorithm on the provided edges (treated as undirected) to compute a minimum spanning forest: one spanning tree per connected component. Negative weights are fine.

  • Returns: Triples containing the from-node, the to-node, and the weight of that tree edge. Since the graph is treated as undirected, the orientation of each reported edge is arbitrary.

MinimumSpanningTreePrim(...)

MinimumSpanningTreePrim(edges[from, to, weight?], starting?[idx])

Runs Prim's algorithm on the provided edges (treated as undirected) to compute a minimum spanning tree. The first row of starting names the starting node; only the connected component containing it is spanned. Supplying an empty starting relation, or a starting node that appears in no edge, is an error. If starting is omitted, which component gets spanned is arbitrary.

  • Returns: Triples containing the from-node, the to-node, and the weight of that tree edge (the from-node is the side already in the tree).

TopSort(...)

TopSort(edges[from, to])

Performs topological sorting on the graph with the provided edges, using Kahn's algorithm. Disconnected graphs are fine: all components are sorted into the one output sequence.

  • Returns: Pairs containing the sort position (starting at 0) and the node.

Caution

Nodes that sit on a directed cycle, and any node reachable only through one, are silently omitted from the output — a cyclic input returns fewer rows rather than raising an error. If you need to detect cycles, compare the output row count with the node count, or run StronglyConnectedComponents first.

edges[from, to] <- [['a', 'b'], ['b', 'c'], ['c', 'a'], ['c', 'd'], ['x', 'y']]
?[position, node] <~ TopSort(edges[])
position  node
0         'x'
1         'y'

a, b and c sit on the cycle; d is on no cycle itself but is reachable only through it, so of the six nodes only the disjoint pair survives.

Pathfinding algorithms

ShortestPathBFS(...)

ShortestPathBFS(edges[from, to], starting[start_idx], goals[goal_idx])

Runs breadth-first search to determine the shortest path between the starting nodes and the goals. Assumes the graph to be directed and all edges to be of unit weight. Ties will be broken in an unspecified way. If you need anything more complicated, use one of the other algorithms below.

  • Returns: Triples containing the starting node, the goal, and a shortest path. An unreachable goal still produces a row, with null in place of the path.

ShortestPathDijkstra(...)

ShortestPathDijkstra(edges[from, to, weight?], starting[idx], goals?[idx], undirected: false, keep_ties: false)

Runs Dijkstra's algorithm to determine the shortest paths between the starting nodes and the goals. Weights, if given, must be non-negative. If goals is omitted, a row is returned for every node of the graph: reachable nodes carry their lowest cost (the starting node itself at cost zero), and unreachable nodes come back with an infinite cost and an empty list in place of the path — filter with is_finite(cost) if you only want the reachable ones. Starting or goal nodes that appear in no edge are silently skipped.

  • undirected: Whether the graph should be interpreted as undirected. Defaults to false.
  • keep_ties: Whether to return all paths with the same lowest cost. Defaults to false, in which case any one path of the lowest cost could be returned.
  • Returns: 4-tuples containing the starting node, the goal, the lowest cost, and a path with the lowest cost.
hops[from, to] := *recalls{from, to}
starting[] <- [['m1']]
goals[] <- [['m5']]
?[start, goal, cost, path] <~ ShortestPathDijkstra(hops[], starting[], goals[])
start  goal  cost  path
'm1'   'm5'  2.0   ['m1', 'm2', 'm5']

Omitting goals asks for the whole distance map from each start. From m6 the connector chain is reachable, but nothing upstream of it is:

hops[from, to] := *recalls{from, to}
starting[] <- [['m6']]
?[start, goal, cost, path] <~ ShortestPathDijkstra(hops[], starting[])
:order cost, goal
start  goal  cost      path
'm6'   'm6'  0.0       ['m6']
'm6'   'm7'  1.0       ['m6', 'm7']
'm6'   'm8'  2.0       ['m6', 'm7', 'm8']
'm6'   'm5'  3.0       ['m6', 'm7', 'm8', 'm5']
'm6'   'm1'  INFINITY  []
'm6'   'm2'  INFINITY  []
'm6'   'm3'  INFINITY  []
'm6'   'm4'  INFINITY  []

The infinite cost is a real float value (it arrives as the string "INFINITY" over the JSON API), so is_finite(cost) drops the unreachable rows.

KShortestPathYen(...)

KShortestPathYen(edges[from, to, weight?], starting[idx], goals[idx], k: expr, undirected: false)

Runs Yen's algorithm (backed by Dijkstra's algorithm) to find the k-shortest paths between nodes in starting and nodes in goals.

  • k (required): How many routes to return for each start-goal pair. Fewer are returned when fewer distinct paths exist.
  • undirected: Whether the graph should be interpreted as undirected. Defaults to false.
  • Returns: 4-tuples containing the starting node, the goal, the cost, and a path with the cost.
hops[from, to] := *recalls{from, to}
starting[] <- [['m1']]
goals[] <- [['m5']]
?[start, goal, cost, path] <~ KShortestPathYen(hops[], starting[], goals[], k: 3)
start  goal  cost  path
'm1'   'm5'  2.0   ['m1', 'm2', 'm5']
'm1'   'm5'  4.0   ['m1', 'm2', 'm3', 'm4', 'm5']

Only two rows despite k: 3: the graph has exactly two distinct paths from m1 to m5.

BudgetedTraversal(...)

BudgetedTraversal(edges[from, to, weight?], seeds[node, initial_cost?], gate?[node, ...], max_nodes: expr, max_cost: expr, max_depth: expr, undirected: false, admit: expr)

mnestic

Specific to mnestic 0.12.0 — not present in upstream CozoDB. It is the context-fill primitive: "give me the N cheapest nodes around what search found, with the paths that reached them", in one call. Works with cached graph projections.

Runs a cheapest-first expansion (multi-source Dijkstra) from the seeds over non-negative edge weights, stopping once max_nodes distinct nodes have been admitted. Accepts graph: 'name' in place of the positional edges relation (the remaining positional inputs shift down by one) and produces byte-identical output in either form.

  • edges: the positional edge relation [from, to, weight?]. A missing weight column means every edge weighs 1.0. Weights must be finite and non-negative (zero is legal); a negative, NaN or infinite weight is an error. Weights are consumed as costs — if your edges carry similarities or confidences, apply a monotone transform such as -ln(weight) yourself.
  • seeds (required): column 0 is the node; optional column 1 is that seed's initial_cost, a finite number ≥ 0 (defaults to 0.0 when absent; a non-numeric value there is an error). Duplicate seed rows keep the minimum initial cost; columns beyond the first two are ignored. A seed absent from the graph is still admitted and emitted — it has no out-edges.
  • gate (optional): a relation whose first column is the node id and whose remaining columns feed admit:. When given, a node (seed or reached) is admissible only if some gate row for it satisfies admit: (bare presence of a row, if admit: is not set). An inadmissible node spends no budget, emits nothing, and is never expanded through — it does not bridge to what lies beyond it. Gate rows are probed lazily per candidate; the relation is never materialized.
  • max_nodes (required): a positive integer — the global budget of distinct admitted nodes, seeds included. Deliberately has no default.
  • max_cost: a finite float ≥ 0. Only nodes whose cheapest admissible path costs at most max_cost are admitted, seeds included. Unset means unbounded; NaN and ±infinity are errors.
  • max_depth: a positive integer — an exact hop ceiling: a node whose cheapest path exceeds the bound is still found through its cheapest within-bound path if one exists (layered labels, never depth-pruned Dijkstra). Unset means unbounded.
  • undirected: whether to traverse each edge in both directions. Defaults to false.
  • admit: an expression evaluated against each probed gate row, with bindings resolved against the gate input's column names (keep them aligned). Requires the gate relation — setting admit: without one is an error.
  • Returns: 4-tuples (node, cost, parent, depth) — the settled shortest-path-tree fragment, one row per admitted node. Roots emit parent = null and depth = 0; cost is a float. Output is deterministic by construction: admission follows the (cost, node) total order, and the parent/depth witness resolves by strict lexicographic relaxation — independent of input form or iteration order.

The traversal honors :timeout and ::kill.

To fill context around the RocksDB decision m2, project the association strengths into distances (stronger recall ⇒ cheaper hop) and ask for the four cheapest memories:

dist[from, to, d] := *recalls{from, to, strength}, d = 10 - strength * 10
seeds[] <- [['m2']]
?[node, cost, parent, depth] <~ BudgetedTraversal(dist[], seeds[], max_nodes: 4)
node  cost  parent  depth
'm2'  0.0   null    0
'm3'  4.0   'm2'    1
'm4'  5.0   'm3'    2
'm5'  6.0   'm2'    1

m5 is reached at cost 6.0 through the direct m2 → m5 edge rather than the chain through m4 (which would cost 6.5), and the parent column records that witness. Now gate the same expansion on important memories only, passing memory itself as the gate (its first column is the id):

dist[from, to, d] := *recalls{from, to, strength}, d = 10 - strength * 10
seeds[] <- [['m2']]
?[node, cost, parent, depth] <~ BudgetedTraversal(
    dist[], seeds[], *memory[id, kind, text, importance, at, v],
    admit: importance >= 0.75, max_nodes: 4)
node  cost  parent  depth
'm2'  0.0   null    0
'm5'  6.0   'm2'    1

m3 (importance 0.7) is gated out, and because a gated-out node never bridges, m4 behind it is not reached either — even though m4 itself would pass the gate. Two rows come back despite the budget of four: the admissible region is exhausted.

BreadthFirstSearch(...)

BreadthFirstSearch(edges[from, to], nodes[idx, ...], starting?[idx], condition: expr, limit: 1)

Runs breadth first search on the directed graph with the given edges and nodes, starting at the nodes in starting. If starting is not given, it will default to all of nodes, which may be quite a lot to calculate.

  • condition (required): The stopping condition, will be evaluated with the bindings given to nodes. Should evaluate to a boolean, with true indicating an acceptable answer was found.
  • limit: How many answers to produce in total (not per starting node) before the search stops. Defaults to 1.
  • Returns: Triples containing the starting node, the answer node, and the found path connecting them.

BFS(...)

BFS(...)

See BreadthFirstSearch.

DepthFirstSearch(...)

DepthFirstSearch(edges[from, to], nodes[idx, ...], starting?[idx], condition: expr, limit: 1)

Runs depth first search on the directed graph with the given edges and nodes, starting at the nodes in starting. If starting is not given, it will default to all of nodes, which may be quite a lot to calculate.

  • condition (required): The stopping condition, will be evaluated with the bindings given to nodes. Should evaluate to a boolean, with true indicating an acceptable answer was found.
  • limit: How many answers to produce in total (not per starting node) before the search stops. Defaults to 1.
  • Returns: Triples containing the starting node, the answer node, and the found path connecting them.

DFS(...)

DFS(...)

See DepthFirstSearch.

ShortestPathAStar(...)

ShortestPathAStar(edges[from, to, weight], nodes[idx, ...], starting[idx], goals[idx], heuristic: expr)

Computes the shortest path from every node in starting to every node in goals by the A* algorithm.

edges are interpreted as directed, weighted edges with non-negative weights. Unlike the other weighted algorithms, non-negativity is assumed rather than checked: only NaN weights are rejected, and a negative weight is silently accepted and can produce incorrect paths.

  • heuristic (required): The search heuristic expression. It will be evaluated with the bindings from goals and nodes. It should return a number which is a lower bound of the true shortest distance from a node to the goal node. If the estimate is not a valid lower-bound, i.e. it over-estimates, the results returned may not be correct.
  • Returns: 4-tuples containing the starting node, the goal node, the lowest cost, and a path with the lowest cost.

Note

The performance of the A* algorithm heavily depends on how good your heuristic function is. Passing in 0 as the estimate is always valid, but then you really should be using Dijkstra's algorithm.

Good heuristics usually come about from a metric in the ambient space in which your data live, e.g. spherical distance on the surface of a sphere, or Manhattan distance on a grid. The haversine_deg_input function could be helpful for the spherical case. Note that you must use the correct units for the distance.

Providing a heuristic that is not guaranteed to be a lower-bound might be acceptable if you are fine with inaccuracies. The errors in the answers are bound by the sum of the margins of your over-estimates.

Community detection algorithms

ClusteringCoefficients(...)

ClusteringCoefficients(edges[from, to])

Computes the clustering coefficients of the graph with the provided edges, treated as undirected. Any columns after the first two are ignored.

  • Returns: 4-tuples containing the node, the clustering coefficient, the number of triangles attached to the node, and the total degree of the node.

CommunityDetectionLouvain(...)

CommunityDetectionLouvain(edges[from, to, weight?], undirected: false, max_iter: 10, delta: 0.0001, keep_depth?: depth)

Runs the Louvain algorithm on the graph with the provided edges, optionally non-negatively weighted.

  • undirected: Whether the graph should be interpreted as undirected. Defaults to false.
  • max_iter: The maximum number of iterations to run within each epoch of the algorithm. Defaults to 10.
  • delta: How much the modularity has to change before a step in the algorithm is considered to be an improvement. Defaults to 0.0001.
  • keep_depth: How many levels in the hierarchy of communities to keep in the final result. If omitted, all levels are kept.
  • Returns: Pairs containing the label for a community, and a node belonging to the community. Each label is a list of integers with maximum length constrained by the parameter keep_depth. This list represents the hierarchy of sub-communities containing the node.

LabelPropagation(...)

LabelPropagation(edges[from, to, weight?], undirected: false, max_iter: 10)

Runs the label propagation algorithm on the graph with the provided edges, optionally weighted.

  • undirected: Whether the graph should be interpreted as undirected. Defaults to false.
  • max_iter: The maximum number of iterations to run. Defaults to 10.
  • Returns: Pairs containing the integer label for a community, and a node belonging to the community.

Centrality measures

DegreeCentrality(...)

DegreeCentrality(edges[from, to], nodes?[idx])

Computes the degree centrality of the nodes in the graph with the given edges. The computation is trivial, so this should be your first thing to try when exploring new data.

  • nodes: An optional second relation naming vertices; a vertex appearing here but in no edge is reported with all-zero degrees rather than dropped.
  • Returns: 4-tuples containing the node, the total degree (how many edges involve this node), the out-degree (how many edges point away from this node), and the in-degree (how many edges point to this node).
?[memory, total, outdeg, indeg] <~ DegreeCentrality(*recalls[])
:order -total
:limit 2
memory  total  outdeg  indeg
'm2'    3      2       1
'm5'    3      0       3

PageRank(...)

PageRank(edges[from, to], nodes?[idx], undirected: false, theta: 0.85, epsilon: 0.0001, iterations: 20)

Computes the PageRank from the given graph with the provided edges.

mnestic

Results changed in mnestic 0.11.0: the default iterations is now 20, up from upstream's 10 — a default that is measurably non-convergent at the default epsilon. Pass iterations: 10 to restore the old numbers. The optional nodes relation and the non-convergence warning also landed in 0.11.0.

  • nodes: An optional second relation naming the vertices. A vertex appearing here but in no edge becomes a real degree-0 vertex: it is ranked, and because it enters N, every other rank changes too. Without it the vertex set is exactly the set of edge endpoints. Cannot be combined with graph: — declare the vertices on the projection with nodes: instead.
  • undirected: Whether the graph should be interpreted as undirected. Defaults to false.
  • theta: A number between 0 and 1 indicating how much weight in the PageRank matrix is due to the explicit edges. A number of 1 indicates no random restarts. Defaults to 0.85.
  • epsilon: Minimum PageRank change in any node for an iteration to be considered an improvement. Defaults to 0.0001. Setting it to 0.0 disables the convergence test, meaning "run exactly iterations".
  • iterations: The maximum number of iterations to run; fewer are run if convergence is reached. Defaults to 20. A warning is logged when this cap stops the run short of epsilon.
  • Returns: Pairs containing the node label and its PageRank.

ClosenessCentrality(...)

ClosenessCentrality(edges[from, to, weight?], undirected: false)

Computes the closeness centrality of the graph. The input relation represents edges connecting nodes, optionally weighted (weights must be non-negative).

  • undirected: Whether the edges should be interpreted as undirected. Defaults to false.
  • Returns: Node together with its centrality.

BetweennessCentrality(...)

BetweennessCentrality(edges[from, to, weight?], undirected: false)

Computes the betweenness centrality of the graph. The input relation represents edges connecting nodes, optionally weighted (weights must be non-negative).

  • undirected: Whether the edges should be interpreted as undirected. Defaults to false.
  • Returns: Node together with its centrality.

Caution

BetweennessCentrality is very expensive for medium to large graphs. If possible, collapse large graphs into supergraphs by running a community detection algorithm first.

Miscellaneous

RandomWalk(...)

RandomWalk(edges[from, to, ...], nodes[idx, ...], starting[idx], steps: expr, weight?: expr, iterations: 1)

Performs random walk on the graph with the provided edges and nodes, starting at the nodes in starting.

  • steps (required): How many steps to walk for each node in starting. Produced paths may be shorter if dead ends are reached.
  • weight: An expression evaluated against bindings of nodes and bindings of edges, at a time when the walk is at a node and choosing between multiple edges to follow. It should evaluate to a non-negative number indicating the weight of the given choice of edge to follow. If omitted, which edge to follow is chosen uniformly.
  • iterations: How many times walking is repeated for each starting node. Defaults to 1.
  • Returns: Triples containing a numerical index for the walk (counting up across all starting nodes and iterations), the starting node, and the path followed.

Adapted from the CozoDB documentation by Ziyang Hu and the Cozo Project Authors, used under CC‑BY‑SA‑4.0. Adaptations for mnestic are released under the same license. mnestic is an independent fork and is not affiliated with or endorsed by the original authors.