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 m1–m8, 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 ───→ m5Two 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 3text 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.047How 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 emptydatawith 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 inrel.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 inrel.descending: Whether the sorting process should be done in descending order. Defaults tofalse.break_ties: Whether ties should be broken, e.g. whether the first two rows with identical sort keys should be given ordering numbers1and2instead of1and1. Defaults tofalse(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 at1.
?[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, usefile://<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,nullwill 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: Iftrue, 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, usefile://<PATH_TO_FILE>.fields(required): A list of field names, for extracting fields from JSON objects into the relation.json_lines: Iftrue, parse the file as lines of JSON objects, each line containing a single object; iffalse, parse the file as a JSON array containing many objects.null_if_absent: Iftrueand a requested field is absent, will outputnullin its place. Iffalseand the requested field is absent, will throw an error.prepend_index: Iftrue, 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 bylist_idand ranked within each list byscore. 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 to60; values below0are clamped to0.descending: Whether a higher score is better within a list. Defaults totrue. Mixed-direction lists (e.g. distances and relevances) should be normalised by the caller, or fused in separate invocations.detailed: Defaults tofalse. Must be a constant boolean, since it changes the output arity.- Returns: Pairs
[item, fused_score]. Withdetailed: 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 -scoreitem score
'm4' 0.0328
'm3' 0.0323
'm5' 0.0159
'm7' 0.0159m4 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].relevancemust be a finite number andvectora vector value; all vectors must share one dimension (a mismatch is an error, not a crash). lambda:1is pure relevance,0pure diversity. Defaults to0.5, clamped to[0, 1].k: How many candidates to select. Defaults to0, meaning all.- Returns: Pairs
[item, rank]in selection order,rankstarting at1.
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 rankitem rank
'm5' 1
'm7' 2
'm4' 3MMR(...)
MMR(...)
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 8StronglyConnectedComponents(...)
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
nullin 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 tofalse.keep_ties: Whether to return all paths with the same lowest cost. Defaults tofalse, 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, goalstart 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 tofalse.- 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 weighs1.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'sinitial_cost, a finite number ≥ 0 (defaults to0.0when 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 feedadmit:. When given, a node (seed or reached) is admissible only if some gate row for it satisfiesadmit:(bare presence of a row, ifadmit: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 mostmax_costare 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 tofalse.admit: an expression evaluated against each probed gate row, with bindings resolved against thegateinput's column names (keep them aligned). Requires thegaterelation — settingadmit:without one is an error.- Returns: 4-tuples
(node, cost, parent, depth)— the settled shortest-path-tree fragment, one row per admitted node. Roots emitparent = nullanddepth = 0;costis 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' 1m5 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' 1m3 (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 tonodes. Should evaluate to a boolean, withtrueindicating 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 tonodes. Should evaluate to a boolean, withtrueindicating 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 fromgoalsandnodes. 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 tofalse.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 tofalse.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 2memory total outdeg indeg
'm2' 3 2 1
'm5' 3 0 3PageRank(...)
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 entersN, every other rank changes too. Without it the vertex set is exactly the set of edge endpoints. Cannot be combined withgraph:— declare the vertices on the projection withnodes:instead.undirected: Whether the graph should be interpreted as undirected. Defaults tofalse.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 to0.0disables the convergence test, meaning "run exactlyiterations".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 ofepsilon.- 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 tofalse.- 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 tofalse.- 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 instarting. Produced paths may be shorter if dead ends are reached.weight: An expression evaluated against bindings ofnodesand bindings ofedges, 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.