From a7bb71df62a6b1d9ecdd3c1a216f48e116b86439 Mon Sep 17 00:00:00 2001 From: MathisHammel Date: Wed, 1 Oct 2025 19:45:47 +0200 Subject: [PATCH 1/6] Implement speed reduction for FA2 prevent_overlapping --- cpp/src/layout/legacy/barnes_hut.cuh | 1 + cpp/src/layout/legacy/bh_kernels.cuh | 16 ++++++++++++---- cpp/src/layout/legacy/exact_fa2.cuh | 1 + cpp/src/layout/legacy/fa2_kernels.cuh | 16 +++++++++++++--- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/cpp/src/layout/legacy/barnes_hut.cuh b/cpp/src/layout/legacy/barnes_hut.cuh index dd25767db0..42f8eda7e2 100644 --- a/cpp/src/layout/legacy/barnes_hut.cuh +++ b/cpp/src/layout/legacy/barnes_hut.cuh @@ -327,6 +327,7 @@ void barnes_hut(raft::handle_t const& handle, old_forces, old_forces + n, swinging, + prevent_overlapping, vertex_mobility, speed, n); diff --git a/cpp/src/layout/legacy/bh_kernels.cuh b/cpp/src/layout/legacy/bh_kernels.cuh index e82b0bd947..0be5eac78a 100644 --- a/cpp/src/layout/legacy/bh_kernels.cuh +++ b/cpp/src/layout/legacy/bh_kernels.cuh @@ -628,11 +628,12 @@ __global__ static __launch_bounds__(THREADS6, FACTOR6) void apply_forces_bh( float* restrict old_dx, float* restrict old_dy, const float* restrict swinging, + const bool prevent_overlapping, const float* restrict vertex_mobility, const float speed, const int n) { - // For evrery vertex + // For every vertex for (int i = threadIdx.x + blockIdx.x * blockDim.x; i < n; i += gridDim.x * blockDim.x) { // Store displacement needed for next iteration. const float dx = (repel_x[i] + attract_x[i]); @@ -642,9 +643,16 @@ __global__ static __launch_bounds__(THREADS6, FACTOR6) void apply_forces_bh( // Update positions float mobility_factor = vertex_mobility ? vertex_mobility[i] : 1.0f; - float factor = mobility_factor * speed / (1.0 + sqrt(speed * swinging[i])); - Y_x[i] += dx * factor; - Y_y[i] += dy * factor; + float factor = speed / (1.0 + sqrt(speed * swinging[i])); + + if (prevent_overlapping) { + factor = 0.1 * factor; + float df = sqrt(dx * dx + dy * dy); + factor = min(factor * df, 10.0f) / df; + } + + Y_x[i] += dx * mobility_factor * factor; + Y_y[i] += dy * mobility_factor * factor; } } diff --git a/cpp/src/layout/legacy/exact_fa2.cuh b/cpp/src/layout/legacy/exact_fa2.cuh index 7c9985c5c8..c66b9dc6ba 100644 --- a/cpp/src/layout/legacy/exact_fa2.cuh +++ b/cpp/src/layout/legacy/exact_fa2.cuh @@ -194,6 +194,7 @@ void exact_fa2(raft::handle_t const& handle, d_old_forces, d_old_forces + n, d_swinging, + prevent_overlapping, vertex_mobility, speed, n, diff --git a/cpp/src/layout/legacy/fa2_kernels.cuh b/cpp/src/layout/legacy/fa2_kernels.cuh index d2cc5534ba..5f6ed17eb5 100644 --- a/cpp/src/layout/legacy/fa2_kernels.cuh +++ b/cpp/src/layout/legacy/fa2_kernels.cuh @@ -313,6 +313,7 @@ __global__ static void update_positions_kernel(float* restrict x_pos, float* restrict old_dx, float* restrict old_dy, const float* restrict swinging, + const bool prevent_overlapping, const float* restrict vertex_mobility, const float speed, const vertex_t n) @@ -320,12 +321,19 @@ __global__ static void update_positions_kernel(float* restrict x_pos, // For every node. for (int i = threadIdx.x + blockIdx.x * blockDim.x; i < n; i += gridDim.x * blockDim.x) { const float mobility_factor = vertex_mobility ? vertex_mobility[i] : 1.0f; - const float factor = mobility_factor * speed / (1.0 + sqrt(speed * swinging[i])); const float dx = (repel_x[i] + attract_x[i]); const float dy = (repel_y[i] + attract_y[i]); - x_pos[i] += dx * factor; - y_pos[i] += dy * factor; + float factor = speed / (1.0 + sqrt(speed * swinging[i])); + + if (prevent_overlapping) { + factor = 0.1 * factor; + float df = sqrt(dx * dx + dy * dy); + factor = min(factor * df, 10.0f) / df; + } + + x_pos[i] += dx * mobility_factor * factor; + y_pos[i] += dy * mobility_factor * factor; old_dx[i] = dx; old_dy[i] = dy; } @@ -341,6 +349,7 @@ void apply_forces(float* restrict x_pos, float* restrict old_dx, float* restrict old_dy, const float* restrict swinging, + const bool prevent_overlapping, const float* restrict vertex_mobility, const float speed, const vertex_t n, @@ -365,6 +374,7 @@ void apply_forces(float* restrict x_pos, old_dx, old_dy, swinging, + prevent_overlapping, vertex_mobility, speed, n); From 526de3c006df9b59f0801f1a294378ba635cbd61 Mon Sep 17 00:00:00 2001 From: MathisHammel Date: Wed, 1 Oct 2025 22:27:59 +0200 Subject: [PATCH 2/6] Fix whitespace in FA2 kernels --- cpp/src/layout/legacy/bh_kernels.cuh | 4 ++-- cpp/src/layout/legacy/fa2_kernels.cuh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/src/layout/legacy/bh_kernels.cuh b/cpp/src/layout/legacy/bh_kernels.cuh index 0be5eac78a..f770690a8e 100644 --- a/cpp/src/layout/legacy/bh_kernels.cuh +++ b/cpp/src/layout/legacy/bh_kernels.cuh @@ -646,9 +646,9 @@ __global__ static __launch_bounds__(THREADS6, FACTOR6) void apply_forces_bh( float factor = speed / (1.0 + sqrt(speed * swinging[i])); if (prevent_overlapping) { - factor = 0.1 * factor; + factor = 0.1 * factor; float df = sqrt(dx * dx + dy * dy); - factor = min(factor * df, 10.0f) / df; + factor = min(factor * df, 10.0f) / df; } Y_x[i] += dx * mobility_factor * factor; diff --git a/cpp/src/layout/legacy/fa2_kernels.cuh b/cpp/src/layout/legacy/fa2_kernels.cuh index 5f6ed17eb5..ee143d53c0 100644 --- a/cpp/src/layout/legacy/fa2_kernels.cuh +++ b/cpp/src/layout/legacy/fa2_kernels.cuh @@ -327,9 +327,9 @@ __global__ static void update_positions_kernel(float* restrict x_pos, float factor = speed / (1.0 + sqrt(speed * swinging[i])); if (prevent_overlapping) { - factor = 0.1 * factor; + factor = 0.1 * factor; float df = sqrt(dx * dx + dy * dy); - factor = min(factor * df, 10.0f) / df; + factor = min(factor * df, 10.0f) / df; } x_pos[i] += dx * mobility_factor * factor; From d1e7df72f3ca43c8d61e0b4880b136614badf744 Mon Sep 17 00:00:00 2001 From: MathisHammel Date: Mon, 6 Oct 2025 22:07:44 +0200 Subject: [PATCH 3/6] Add epsilon to prevent division by 0 --- cpp/src/layout/legacy/bh_kernels.cuh | 2 +- cpp/src/layout/legacy/fa2_kernels.cuh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/src/layout/legacy/bh_kernels.cuh b/cpp/src/layout/legacy/bh_kernels.cuh index f770690a8e..a98782702c 100644 --- a/cpp/src/layout/legacy/bh_kernels.cuh +++ b/cpp/src/layout/legacy/bh_kernels.cuh @@ -647,7 +647,7 @@ __global__ static __launch_bounds__(THREADS6, FACTOR6) void apply_forces_bh( if (prevent_overlapping) { factor = 0.1 * factor; - float df = sqrt(dx * dx + dy * dy); + float df = sqrt(dx * dx + dy * dy + FLT_EPSILON); factor = min(factor * df, 10.0f) / df; } diff --git a/cpp/src/layout/legacy/fa2_kernels.cuh b/cpp/src/layout/legacy/fa2_kernels.cuh index ee143d53c0..7a80e6b968 100644 --- a/cpp/src/layout/legacy/fa2_kernels.cuh +++ b/cpp/src/layout/legacy/fa2_kernels.cuh @@ -328,7 +328,7 @@ __global__ static void update_positions_kernel(float* restrict x_pos, if (prevent_overlapping) { factor = 0.1 * factor; - float df = sqrt(dx * dx + dy * dy); + float df = sqrt(dx * dx + dy * dy + FLT_EPSILON); factor = min(factor * df, 10.0f) / df; } From 38575825d1f26213f6fdfd4b42c76c868130bc1d Mon Sep 17 00:00:00 2001 From: MathisHammel Date: Mon, 20 Oct 2025 18:29:17 +0200 Subject: [PATCH 4/6] Fix thresholds in FA2 noverlap tests --- .../cugraph/tests/layout/test_force_atlas2.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/python/cugraph/cugraph/tests/layout/test_force_atlas2.py b/python/cugraph/cugraph/tests/layout/test_force_atlas2.py index fe3a5e7258..a684c5f1b6 100644 --- a/python/cugraph/cugraph/tests/layout/test_force_atlas2.py +++ b/python/cugraph/cugraph/tests/layout/test_force_atlas2.py @@ -125,11 +125,11 @@ def cugraph_call( ] DATASETS_NOVERLAP = [ - (karate, 10.0, 50), - (polbooks, 10.0, 90), - (dolphins, 10.0, 60), - (netscience, 10.0, 1100), - (dining_prefs, 10.0, 20), + (karate, 10.0, 100), + (polbooks, 10.0, 200), + (dolphins, 10.0, 110), + (netscience, 5.0, 500), + (dining_prefs, 10.0, 80), ] DATASETS_MOBILITY = [ @@ -142,6 +142,7 @@ def cugraph_call( MAX_ITERATIONS = [500] +NOVERLAP_MAX_ITERATIONS = [5000] BARNES_HUT_OPTIMIZE = [False, True] MOBILITY_FIXED_CNT = [5, 12] @@ -223,8 +224,9 @@ def test_force_atlas2(graph_file, score, max_iter, barnes_hut_optimize): @pytest.mark.sg @pytest.mark.parametrize("graph_file, radius, max_overlaps", DATASETS_NOVERLAP) -@pytest.mark.parametrize("max_iter", MAX_ITERATIONS) -def test_force_atlas2_noverlap(graph_file, radius, max_overlaps, max_iter): +@pytest.mark.parametrize("max_iter", NOVERLAP_MAX_ITERATIONS) +@pytest.mark.parametrize("barnes_hut_optimize", BARNES_HUT_OPTIMIZE) +def test_force_atlas2_noverlap(graph_file, radius, max_overlaps, max_iter, barnes_hut_optimize): """ All vertices are given the same radius. After running FA2 with prevent_overlapping enabled, the number of pairs of overlapping @@ -253,6 +255,8 @@ def count_overlaps(cu_pos, radius): } ) + vertex_radius = vertex_radius.astype({"radius": "float32"}) + cu_pos = cugraph_call( cu_M, max_iter=max_iter, @@ -264,7 +268,7 @@ def count_overlaps(cu_pos, radius): overlap_scaling_ratio=100.0, edge_weight_influence=1.0, jitter_tolerance=1.0, - barnes_hut_optimize=False, + barnes_hut_optimize=barnes_hut_optimize, barnes_hut_theta=0.5, scaling_ratio=2.0, strong_gravity_mode=False, @@ -296,6 +300,7 @@ def test_force_atlas2_mobility(graph_file, max_iter, fixed_node_cnt): random.shuffle(mobility) mobility_df = cudf.DataFrame({"vertex": vertices, "mobility": mobility}) + mobility_df = mobility_df.astype({"mobility": "float32"}) # Initial layout without mobility pos_1 = cugraph_call( From 03d1491ce4a73508446cbe914ff3db68d77cd281 Mon Sep 17 00:00:00 2001 From: MathisHammel Date: Mon, 20 Oct 2025 18:45:19 +0200 Subject: [PATCH 5/6] Update FA2 test threshold for noverlap --- python/cugraph/cugraph/tests/layout/test_force_atlas2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cugraph/cugraph/tests/layout/test_force_atlas2.py b/python/cugraph/cugraph/tests/layout/test_force_atlas2.py index a684c5f1b6..a1047da303 100644 --- a/python/cugraph/cugraph/tests/layout/test_force_atlas2.py +++ b/python/cugraph/cugraph/tests/layout/test_force_atlas2.py @@ -128,7 +128,7 @@ def cugraph_call( (karate, 10.0, 100), (polbooks, 10.0, 200), (dolphins, 10.0, 110), - (netscience, 5.0, 500), + (netscience, 5.0, 700), (dining_prefs, 10.0, 80), ] From f3545d768fbe5d1fdb8a68af7ff02ed92549ba9d Mon Sep 17 00:00:00 2001 From: MathisHammel Date: Mon, 20 Oct 2025 20:18:59 +0200 Subject: [PATCH 6/6] Lint ForceAtlas2 test file --- python/cugraph/cugraph/tests/layout/test_force_atlas2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/cugraph/cugraph/tests/layout/test_force_atlas2.py b/python/cugraph/cugraph/tests/layout/test_force_atlas2.py index 5814799115..76c117c121 100644 --- a/python/cugraph/cugraph/tests/layout/test_force_atlas2.py +++ b/python/cugraph/cugraph/tests/layout/test_force_atlas2.py @@ -235,7 +235,9 @@ def test_force_atlas2(graph_file, score, max_iter, barnes_hut_optimize): @pytest.mark.parametrize("graph_file, radius, max_overlaps", DATASETS_NOVERLAP) @pytest.mark.parametrize("max_iter", NOVERLAP_MAX_ITERATIONS) @pytest.mark.parametrize("barnes_hut_optimize", BARNES_HUT_OPTIMIZE) -def test_force_atlas2_noverlap(graph_file, radius, max_overlaps, max_iter, barnes_hut_optimize): +def test_force_atlas2_noverlap( + graph_file, radius, max_overlaps, max_iter, barnes_hut_optimize +): """ All vertices are given the same radius. After running FA2 with prevent_overlapping enabled, the number of pairs of overlapping