8.0 KiB
Radial Sugiyama vs Go Snapshot Pipeline
This note delimits the algorithmic intersection between the Rust pipeline in radial_sugiyama/ and the Go snapshot/export path in:
backend_go/graph_export.gobackend_go/graph_snapshot.go
The goal is not to describe integration mechanics yet, but to mark where the two implementations solve the same problem, where they only touch indirectly, and where they are solving different problems.
Scope
The Rust pipeline is a hierarchy-specific layout pipeline:
- import ontology hierarchy from Turtle
- optionally filter to a rooted descendant subtree
- validate DAG structure
- assign hierarchy levels
- insert dummy nodes for long edges
- reduce crossings
- assign coordinates
- project to radial space
- generate routed edge artifacts
- export SVG
The Go path is a snapshot/materialization pipeline:
- query predicates and edges from SPARQL
- accumulate nodes and edges
- build a graph response
- run a lightweight hierarchy layering + radial placement
- attach labels
- return JSON to the frontend
Because of that, the true intersection is narrow in graph_export.go and broader in the layout section of graph_snapshot.go.
Legend
Direct overlap: both sides implement essentially the same algorithmic concernAdjacent overlap: one side prepares or consumes the same kind of structure, but the algorithm differs materiallyNo overlap: the stage exists only on one side
Intersection with graph_export.go
graph_export.go overlaps with the Rust pipeline only at graph materialization time.
| Algorithmic stage | Rust pipeline | graph_export.go |
Intersection | Notes |
|---|---|---|---|---|
| Node identity and deduplication | ttl.rs maps class IRIs to stable node indices |
graphAccumulator.getOrAddNode maps SPARQL terms to stable node IDs |
Direct overlap | Both build a unique node set from repeated source records. |
| Edge materialization | ttl.rs emits superclass -> subclass edges and deduplicates repeats |
graphAccumulator.addBindings emits source -> target edges from SPARQL bindings |
Adjacent overlap | Both convert raw triples/bindings into an in-memory graph, but Rust is specialized to rdfs:subClassOf while Go is predicate-agnostic. |
| Literal / blank-node filtering | ttl.rs ignores blank/literal hierarchy endpoints |
getOrAddNode skips literals and optionally keeps blank nodes |
Adjacent overlap | Similar sanitation step, but not identical semantics. |
| Predicate preservation | Rust discards all predicates except rdfs:subClassOf |
Go preserves predicate IDs through PredicateDict |
No overlap | This is Go-only in the compared files. |
| Graph limits / capacity management | Rust does not enforce snapshot-style node and edge caps here | Go enforces nodeLimit and preallocates with edge hints |
No overlap | This is an operational concern of the Go snapshot path. |
Boundary for graph_export.go
The clean algorithmic seam is:
- Go owns generic SPARQL binding ingestion and generic graph materialization.
- Rust owns hierarchy-specialized interpretation once a hierarchy graph has already been isolated.
That means graph_export.go is not competing with the Rust layout pipeline. It is only producing the kind of node/edge structure that Rust would eventually need as input.
Intersection with graph_snapshot.go
graph_snapshot.go intersects with the Rust pipeline in two different regions:
- graph acquisition and hierarchy preparation
- lightweight layout assignment
Stage-by-stage comparison
| Algorithmic stage | Rust pipeline | graph_snapshot.go |
Intersection | Notes |
|---|---|---|---|---|
| Source acquisition | graph_from_ttl_path parses Turtle directly |
fetchGraphSnapshot queries SPARQL in batches |
Adjacent overlap | Both acquire a graph, but from different upstream sources. |
| Hierarchy graph extraction | Rust keeps only rdfs:subClassOf during import |
Go accepts a graph_query_id and accumulates whatever that query returns |
Adjacent overlap | The overlap is meaningful only when the Go query is hierarchy-like. |
| Rooted subtree filtering | filter_graph_to_descendants keeps one configured root and its descendants |
No equivalent in these two Go files | No overlap | This is currently Rust-only. |
| Cycle detection / DAG validation | compute_hierarchy_levels rejects cyclic graphs |
levelSynchronousKahnLayers returns a CycleError if not all nodes are processed |
Direct overlap | Both need a DAG to continue with hierarchy layout. |
| Level assignment | Rust computes longest-path hierarchy levels | Go computes level-synchronous Kahn layers | Direct overlap | Same problem, different algorithm. Both assign ring depth from DAG structure. |
| Per-level ordering | Rust later optimizes order for crossings | Go sorts each layer lexicographically by IRI | Adjacent overlap | Both define an order inside a level, but Go is a simple deterministic ordering while Rust is layout-driven. |
| Radial node placement | Rust projects coordinates to rings after Sugiyama coordinate assignment | Go uses radialPositionsFromLayers to place each layer on a ring |
Direct overlap | Same output shape, very different sophistication. |
| Coordinate shifting / scaling controls | Rust has configurable radius, spacing, borders, and positive-coordinate shifting | Go uses a fixed maxR = 5000.0 radial envelope |
Adjacent overlap | Both map levels to 2D coordinates, but only Rust exposes tuned geometry controls. |
| Label enrichment | Rust keeps node labels as imported IRIs | Go fetches rdfs:label after layout |
Adjacent overlap | Both carry node naming, but the enrichment algorithm is currently Go-only. |
| Response packaging | Rust writes SVG and layout artifacts | Go returns GraphResponse JSON plus metadata |
No overlap | Same graph, different downstream consumers. |
Rust-only algorithms with no counterpart in the compared Go files
These parts of the Rust pipeline do not currently intersect with graph_export.go or graph_snapshot.go:
- rooted descendant filtering
- dummy-node insertion for long edges
- crossing reduction / sifting
- coordinate assignment before radial projection
- adaptive / packed / distributed ring projection modes
- routed edge generation
- layout artifact generation
- SVG rendering and export
These are the parts that make the Rust pipeline a true Sugiyama-style layout engine rather than a simple radial snapshot placer.
Go-only algorithms with no counterpart in the Rust pipeline
These parts of the compared Go files do not currently exist in Rust:
- predicate dictionary construction from SPARQL results
- batched SPARQL edge fetching with memory management
- snapshot limits and backend metadata packaging
rdfs:labellookup through SPARQL- generic graph export over arbitrary predicate sets
These are acquisition and serving concerns rather than layout concerns.
Algorithmic ownership boundary
If the future integration wants a clean division of responsibility, the strongest ownership boundary is:
Go-owned stages
- query execution against AnzoGraph / SPARQL
- predicate-aware graph accumulation
- generic graph snapshot materialization
- label fetching and API response orchestration
Rust-owned stages
- hierarchy-specific filtering
- hierarchy-level assignment
- Sugiyama expansion with dummy nodes
- crossing minimization
- coordinate assignment
- radial projection and route generation
- layout artifact production
Most important practical conclusion
At algorithm granularity, the Rust pipeline intersects only lightly with graph_export.go, but it intersects substantially with the hierarchy-layout portion of graph_snapshot.go.
The main replacement candidates in a future integration are therefore not the generic export/materialization routines in graph_export.go, but these hierarchy-layout steps currently performed by graph_snapshot.go:
- DAG validation / cycle detection
- layer assignment
- per-layer ordering
- radial coordinate generation
Everything after that depends on how much of the Rust layout artifact model the future integration wants to expose to the frontend.