Graph authoring

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 hops

Grid 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.edges

path.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.summary

Membership 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_minutes

Node 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 peer

A 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 road

limit 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 iterations

Available 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 regions

Each 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.