Graph Authoring
Graph Authoring
Grid graphs are immutable, directed or undirected property multigraphs. They retain stable node and edge identity, property schemas, deterministic ordering, and explicit work bounds. A graph is a named resident value rather than a workbook cell or spilled array.
Use graphs when topology is part of the problem: routes, dependencies, communities, flows, reachability, propagation, or relationships that cannot be represented honestly as independent table rows.
Declaring A Graph
Use an exact graph named binding:
frame road_rows = READ_PARQUET("roads.parquet")
graph roads = GRAPH(
road_rows AS edges,
"road_id" AS edge_id,
"origin" AS from,
"destination" AS to,
["minutes", "capacity", "build_cost"] AS edge_properties,
TRUE AS directed
)edges, edge_id, from, and to are required. The edge source may be a
Frame, finite range, or literal row array. Stable edge IDs are mandatory.
Optional nodes and node_id bindings add isolated nodes.
node_properties and edge_properties select retained properties; when they
are omitted, Grid retains non-binding columns.
Parallel edges and self-loops are supported. Construction rejects missing endpoints, duplicate edge IDs, invalid keys, and inconsistent rows.
Node identity is exact and type-sensitive. Strings and checked integers are valid node keys. Blanks, floating-point identity, and implicit text coercion are rejected.
Graph values must use unqualified named bindings. They cannot be assigned to a
cell, range, sheet-qualified target, input, output, default, lazy
assignment, or scheduled assignment.
GRAPH also accepts one native parse_tree source. This overload derives a
directed parent-to-child property Graph at runtime from the exact tree cell
revision:
tree = PARSE_RESULT_TREE(PARSE(source_text, parser))
graph syntax = GRAPH(tree)Parse-tree Graphs use the common resident Graph algorithms and Predicate
projection path. Their node IDs are deterministic one-based preorder ordinals
within a tree revision; child order is the explicit ordinal edge property,
not adjacency iteration order. See
Grammars And Parsers for the
complete property and provenance schema.
Typed Property Schemas
Use a closed schema when algorithms and property access should be checked before execution:
graph schema Roads {
node kind City
edge kind Road: City -> City
node.kind: symbol
node.population: number?
edge.kind: symbol
edge.minutes: number
edge.capacity: number
edge.open: boolean
}
graph roads: Roads = GRAPH(
city_rows AS nodes,
"city_id" AS node_id,
["kind", "population"] AS node_properties,
road_rows AS edges,
"road_id" AS edge_id,
"origin" AS from,
"destination" AS to,
["kind", "minutes", "capacity", "open"] AS edge_properties
)Schema entries use node.<path>: <type> or edge.<path>: <type>. A trailing
? marks an optional property. Nested paths are allowed. Property types use
normal Grid tags, including semantic and dimensional tags whose values can be
represented as graph properties.
Named node and edge kinds constrain valid endpoints. A schema that declares
kinds must include total node.kind and edge.kind string or symbol
properties. Unknown kinds, missing required values, undeclared properties, and
invalid endpoint combinations are errors.
Selectors, objectives, patterns, comprehensions, rewrites, and fixed points are checked against the declared schema. Numeric objectives require non-optional numeric properties. Transformations that preserve source properties also preserve the source schema.
Bare graph bindings remain available when a closed schema is unnecessary.
Querying A Graph
Method form is preferred:
node_count = roads.NODE_COUNT()
out_degree = roads.OUT_DEGREE("Denver")
connected = roads.HAS_PATH("Denver", "Boulder")
frame fastest = roads.SHORTEST_PATH(
"Denver",
"Boulder",
"minutes" AS weight
)
frame ranks = roads.PAGE_RANK(
"capacity" AS weight,
0.85 AS damping,
1e-8 AS tolerance,
100 AS max_iterations
)Scalar operations include NODE_COUNT, EDGE_COUNT, DENSITY, DEGREE,
IN_DEGREE, OUT_DEGREE, HAS_PATH, HAS_EULERIAN_PATH, and
TRIANGLE_COUNT.
Frame-producing operations include NODES, EDGES, NEIGHBORS,
IN_NEIGHBORS, BFS, DFS, SHORTEST_PATH, BELLMAN_FORD,
WEAK_COMPONENTS, STRONG_COMPONENTS, TOPOLOGICAL_ORDER, PAGE_RANK,
bounded DIAMETER, ARTICULATION_POINTS, BRIDGES,
CLOSENESS_CENTRALITY, BETWEENNESS_CENTRALITY, K_CORE,
CLUSTERING_COEFFICIENTS, and MAXIMUM_BIPARTITE_MATCHING.
Weighted algorithms name an edge property for each call. SHORTEST_PATH
rejects negative weights; use BELLMAN_FORD when negative weights are part of
the model. Iterative and all-sources operations require convergence or source
bounds where documented.
Declarative Paths
Use a first-class path when the intent is an objective rather than a specific
algorithm:
path fastest = path in roads from "Denver" to "Boulder"
minimize sum(edge.minutes)
limit 40 AS hops
path resilient = path in roads from "Denver" to "Boulder"
maximize min(edge.capacity)
path fewest_transfers = path in roads from origin to destination
minimize hopsGrid selects an appropriate plan from the graph properties, objective, and bounds. The selected plan may be inspected, but it is not part of source meaning.
Path values are bound to the source graph revision. Read scalar and tabular projections explicitly:
cost = fastest.cost
hops = fastest.hops
frame fastest_nodes = fastest.nodes
frame fastest_edges = fastest.edgespath.nodes contains (ordinal, node). path.edges contains (ordinal, edge, from, to) followed by retained edge properties. A missing path produces
blank scalar projections and empty Frames.
Partitions
Connectivity, core decomposition, and community detection produce the same partition value:
partition weak = partition nodes of roads by connectivity(weak AS direction)
partition cores = partition nodes of roads by core_number
partition core4 = partition nodes of roads by k_core(4 AS k)
partition regions = partition nodes of roads by community(
leiden AS method,
modularity(1 AS resolution) AS objective,
edge.capacity AS weight,
symmetrize(sum) AS direction,
0 AS seed,
all AS hierarchy,
50 AS max_passes,
16 AS max_levels,
0.000000001 AS tolerance
)Leiden is the recommended community method. louvain is available as an
explicit compatibility choice. Directedness is never guessed: choose
directed, undirected, or an explicit symmetrization policy.
Partition projections are Frames:
frame membership = regions.members
frame summaries = regions.summaryMembership contains (node, partition). Summary contains (partition, size, first_node, label). IDs and rows use canonical deterministic order.
Bounded Pattern Matching
Graph patterns always require an output bound:
frame alternatives = match roads limit 1000:
(a)-[first where first.open = TRUE]->(b)-[second where second.open = TRUE]->(c)
return a as origin, b as via, c as destination,
first.minutes as first_minutes, second.minutes as second_minutesNode and edge bindings are statically scoped. Predicates use typed properties, and projections require unique aliases. Repeating a binding with the same role expresses identity equality:
frame mutual = match roads limit 1000:
(a)-[out]->(b)-[back]->(a)
return a.id as origin, b.id as peerA binding cannot change between node and edge roles. Pattern output is a Frame; use ordinary relational operations afterward for deduplication, grouping, aggregation, ordering, or windows.
Comprehensions And Rewrites
A graph comprehension creates a property-preserving filtered graph:
graph active: Roads = roads {
node city where city.population > 0
edge road where road.open = TRUE
}An immutable rewrite applies a bounded set of graph edits and publishes either the complete revised graph or an error. The current form deletes one matched edge binding:
graph normalized: Roads = rewrite roads limit 100000:
match (a)-[road where road.open = FALSE]->(b)
delete roadlimit bounds distinct stable edge IDs. If the edit set exceeds the limit,
the rewrite fails without publishing a partial graph.
Bounded Fixed Points
Use fixpoint for repeated propagation over graph topology:
frame minutes_from_origin = fixpoint in roads:
using min_plus(edge.minutes AS weight)
along AS direction
1000000000000 AS default
0 AS seed origin
until max_delta < 0.000000001
within 100 iterationsAvailable operators are sum_product, min_plus, max_min, and
boolean_or. Every fixed point requires at least one seed and a positive
literal iteration bound. Direction, defaults, seeds, convergence, and numeric
requirements are validated before execution.
Transformations
Graph-producing operations must be rebound as graphs:
graph reverse_roads = roads.REVERSE()
graph least_cost_network = roads.MINIMUM_SPANNING_TREE("build_cost" AS weight)
graph regional = roads.INDUCED_SUBGRAPH(region_node_ids)
graph quotient = contract roads by regionsEach result is an immutable graph revision. Contraction turns each partition into a node, removes internal edges, and preserves cross-partition edge IDs and properties. The partition must belong to the exact source revision.
Results, Ordering, And Bounds
Graph values do not spill into workbook cells. Scalar projections may be bound to cells or names; tabular projections are Frames and require an explicit conversion when workbook cells are needed.
Graph operations use canonical type-sensitive node order, stable edge IDs, and source order as deterministic tie-breakers. Results do not depend on insertion order or worker timing.
Operations enforce limits on nodes, edges, resident size, result rows, work, iterations, temporary memory, deadlines, and cancellation. Approximate all-sources operations report or retain the author-supplied source bound. Exceeded limits produce diagnostics rather than silent truncation or partial edits.
DENSITY measures the simple loop-free projection: parallel edges count once
and self-loops do not count. Graphs with fewer than two nodes report zero.
For exact signatures, use the functions.md catalog. For
querying Graph-produced Frames, see
relational-authoring.md. For the complete feature
status, see features.md.