ForceAtlas2
UDTF: cugraph_force_atlas2
Official cuGraph reference: C API
Place vertices in two dimensions with a force-directed simulation that attracts connected vertices and repels vertices from one another.
Quickstart
The call below supplies edges from registered relation target_edges with canonical src and dst columns and may include weight.
SELECT *
FROM cugraph_force_atlas2(
edges => (SELECT src, dst FROM target_edges)
);
Inputs
Every relation is a named parenthesized SELECT subquery. The required edges role uses canonical src and dst columns. Every role, its canonical columns, and their accepted Arrow types are listed under Relation arguments. Metadata validation resolves registered tables named in its JSON request.
The vertex-ID types this UDTF accepts, the numeric types a per-call codebook maps to native ids, and its string-key limits are listed in Vertex ID support; the concrete call-specific schema comes from gpu_validate_call.
Arguments and options
Relation arguments
Vertex columns of vertex_attributes, initial_positions must use the same vertex domain as edges: the integer type of src and dst, or any listed string type when the endpoints are strings. Every edge_id column must have the integer type of src and dst; string-keyed graphs accept no edge IDs.
Named value arguments
Graph construction options
This UDTF requires directed=false (undirected/symmetric graph). All other graph construction options follow the shared defaults documented in Graph Construction Options.
Output
These are generic descriptor schemas. Run gpu_validate_call to get the concrete, table-specific output schema.
Examples
- MerRec
- Citation network
Where do brand neighborhoods appear when shared fans determine the map?
The MerRec map places brands with shared fans nearby. Medical scrubs, golf, cameras, and bridal wear appear as separate regions. A merchandising team can inspect these groups before testing navigation collections.
The query derives distinct shopper-brand likes, scores the 5,000 brands with the most fans using Jaccard similarity, and keeps each brand's five nearest links for the layout. Leiden assigns colors from brand pairs whose Jaccard score reaches 0.02.
WITH fans AS (
SELECT -(e.user_id + 1) AS src, CAST(i.brand_id AS BIGINT) AS dst
FROM merrec_events e JOIN merrec_items i ON i.item_id = e.item_id
WHERE e.event_type = 'item_like' AND i.brand_id <> 0
GROUP BY e.user_id, i.brand_id
), top_brands AS (
SELECT dst AS brand_id, COUNT(*) AS fans FROM fans GROUP BY dst ORDER BY fans DESC, dst LIMIT 5000
), similar AS (
SELECT first, second, similarity
FROM cugraph_jaccard(
edges => (SELECT src, dst FROM fans),
vertex_pairs => (SELECT a.brand_id AS first, b.brand_id AS second
FROM top_brands a JOIN top_brands b ON a.brand_id < b.brand_id),
directed => false)
WHERE similarity >= 0.02
), nearest AS (
SELECT a, b, similarity,
ROW_NUMBER() OVER (PARTITION BY a ORDER BY similarity DESC, b) AS rk
FROM (SELECT first AS a, second AS b, similarity FROM similar
UNION ALL SELECT second AS a, first AS b, similarity FROM similar) d
), worlds AS (
SELECT vertex AS brand_id, community AS world
FROM cugraph_leiden(
edges => (SELECT first AS src, second AS dst, CAST(similarity AS DOUBLE) AS weight FROM similar),
directed => false, resolution => 3, seed => 42)
), layout AS (
SELECT vertex AS brand_id, x, y
FROM cugraph_force_atlas2(
edges => (SELECT a AS src, b AS dst, CAST(similarity AS DOUBLE) AS weight FROM nearest WHERE rk <= 5),
directed => false, max_iter => 2000, barnes_hut_optimize => true,
outbound_attraction_distribution => true, lin_log_mode => true,
scaling_ratio => 5, gravity => 5, seed => 42)
), brands AS (
SELECT CAST(brand_id AS BIGINT) AS brand_id, MIN(brand_name) AS brand_name
FROM merrec_items WHERE brand_id <> 0 GROUP BY brand_id
)
SELECT l.brand_id, b.brand_name, t.fans, w.world, l.x, l.y
FROM layout l
JOIN worlds w ON w.brand_id = l.brand_id
JOIN top_brands t ON t.brand_id = l.brand_id
JOIN brands b ON b.brand_id = l.brand_id
ORDER BY l.brand_id;
Hover or tap a dot for its brand, world and fans.
- Unlabelled worlds2,073
Positions show neighborhoods, without a meaningful distance unit. Each region's label summarizes its leading brands. Layout coordinates can change between executions, so the map supports inspection of regions rather than precise distances between individual dots.
How are the papers around the Transformer connected?
The selected graph contains Attention is all you need, its 22 references and its 110 most-cited citers in the April 2020 snapshot. The layout shows the links among those papers with their titles attached.
CREATE OR REPLACE VIEW attention_seed AS
SELECT paper_id FROM papers
WHERE title = 'Attention is all you need' AND year = 2017;
-- Include the seed, its references and its 110 most-cited citers.
-- Rank citers by the catalog's n_citation count.
CREATE OR REPLACE VIEW attention_ego_nodes AS
SELECT paper_id FROM (
SELECT e.dst AS paper_id
FROM citation_edges e JOIN attention_seed s ON s.paper_id = e.src
UNION ALL
SELECT src AS paper_id FROM (
SELECT e.src, p.n_citation
FROM citation_edges_by_dst e
JOIN attention_seed s ON s.paper_id = e.dst
JOIN papers p ON p.paper_id = e.src
ORDER BY p.n_citation DESC, e.src LIMIT 110) t
UNION ALL
SELECT paper_id FROM attention_seed
) u GROUP BY paper_id;
-- Keep only citations with both endpoints in the selected neighborhood.
CREATE OR REPLACE VIEW attention_ego_edges AS
SELECT e.src, e.dst
FROM citation_edges e
JOIN attention_ego_nodes a ON a.paper_id = e.src
JOIN attention_ego_nodes b ON b.paper_id = e.dst;
-- Use the same subgraph for positions (ForceAtlas2) and group IDs (Louvain).
WITH layout AS (
SELECT vertex, x, y
FROM cugraph_force_atlas2(
edges => (SELECT src, dst FROM attention_ego_edges), max_iter => 500, seed => 42)),
community AS (
SELECT vertex, community
FROM cugraph_louvain(edges => (SELECT src, dst FROM attention_ego_edges)))
SELECT l.vertex, l.x, l.y, c.community, p.title, p.year
FROM layout l
JOIN community c ON c.vertex = l.vertex
JOIN papers p ON p.paper_id = l.vertex;
The query took 0.93 s.
The saved illustration contains 133 papers and 393 citation links. Of those links, 261 connect papers other than the Transformer seed. The Sigma view supports hover, selection, dragging and zoom. Its browser layout also moves nodes, so displayed positions are not a fixed plot of a single SQL result.
Every selected paper links to the Transformer by construction. The other links, including those involving LSTM and Adam, point to related references inside the neighborhood. The saved group names are display annotations.
A fixed seed controls initial placement, but parallel force accumulation
can produce different coordinates across runs. Save coordinates when later
comparisons need the same positions. The
ForceAtlas2 reference includes an
example that reuses a layout as the initial positions for another pass.
Can I spread overlapping papers apart without starting the map over?
The first pass saves coordinates in a table. The second pass uses that table as its initial positions and supplies radius and mass for every vertex, then enables overlap prevention:
CREATE OR REPLACE TABLE initial_layout AS
SELECT vertex, x, y
FROM cugraph_force_atlas2(
edges => (SELECT src, dst FROM attention_ego_edges), max_iter => 250, seed => 42);
CREATE OR REPLACE VIEW layout_attributes AS
SELECT vertex,
CAST(0.75 AS REAL) AS radius,
CAST(1.0 AS REAL) AS mass
FROM initial_layout;
SELECT vertex, x, y
FROM cugraph_force_atlas2(
edges => (SELECT src, dst FROM attention_ego_edges), max_iter => 250, seed => 42,
prevent_overlapping => true,
vertex_attributes => (SELECT vertex, radius, mass FROM layout_attributes),
initial_positions => (SELECT vertex, x, y FROM initial_layout));
Both side inputs reject null, duplicate, missing, or extra vertices. Radius,
mass, mobility, and coordinates are Float32. Their vertex column must match
the edge endpoint domain.
Limits
- vertex_attributes and initial_positions must contain exactly one non-null row for every graph vertex
- duplicate, missing, and extra ForceAtlas2 side-input vertices are rejected at execution
- radius, mobility, mass, x, and y side-input columns must be Float32
- prevent_overlapping=true requires a radius binding
- seed sets only the initial positions; coordinates differ between runs because force accumulation uses floating-point atomics